6

std::move can be used to explicitly allow move semantics when the move wouldn't be already allowed implicitly (such as often when returning a local object from a function).

Now, I was wondering (esp. in the context of local return and implicit move there), if there is such a thing as the inverse of std::movethat will prevent moving the object (but still allow copying).

Does this even make sense?

Community
  • 1
  • 1
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • 2
    I do have a question though: *Why do you need this?* There may not even be a local object to be returned, and it could just be created via (N)RVO - a theoratical `return std::copy_only(v);` would hinder the compiler in doing that. – Xeo Jul 09 '13 at 08:10
  • delete move construction/assignment. Still wouldn't prevent RVO. – sehe Jul 09 '13 at 08:12
  • 2
    `std::unmove`. Could've also been `std::demove`. Or possibly `antimove` or `dontmove`? `movewithoutmoving`? `movebackintime`, `moveelephanttoblockthepath`? No? – Markus Meskanen Jul 09 '13 at 08:21
  • @Xeo - I *don't* need it :-) Just curious. – Martin Ba Jul 09 '13 at 09:00
  • 3
    For the record: I do **not** think this is a duplicate of the linked question. (Just actually read the question and its answers to see why). – Martin Ba Jul 09 '13 at 09:07
  • @Xeo C++ forbids taking address of temporary. So any API where you need to pass pointer to temp value can benefit `getaddrinfo(ip, port, &static_cast(addrinfo{ .ai_family = AF_INET }), &host);` – OwnageIsMagic Sep 27 '21 at 04:54

3 Answers3

4

std::move converts an lvalue into an rvalue, and it does this essentially by static_cast. The closest to what I can think of as the opposite are these two type casts:

static_cast<T &>(/*rvalue-expression*/)
static_cast<const T&>(/*rvalue-expression*/)

An example of this can be seen below:

#include <iostream>

void f(const int &)
{ std::cout << "const-lval-ref" << std::endl; }

void f(int &&)
{ std::cout << "rval-ref" << std::endl; }

int main()
{
  f(static_cast<const int &>(3));
  return 0;
}

Casting the prvalue 3 to const int & ensures that the lvalue-overload of f is chosen.

In most contexts, you get this rvalue-to-lvalue modification automatically, simply by assigning to a variable:

int a = 3;

When you use a after this line, it will be an lvalue. This is true even when a is declared as rvalue reference:

int &&a = 3;

Here, too, a becomes an lvalue (basically because it "has a name").

The only situation where I can imagine an explicit cast to have any relevant effect is my first example above. And there, when dealing with prvalues like 3 or temporaries returned from function calls by copy, the only legal cast is to const-reference (a non-const-reference is not allowed to bind to a prvalue).

jogojapan
  • 68,383
  • 11
  • 101
  • 131
  • Would static cast to a const-reference `static_cast` really prevent the implicit move on function return? – Martin Ba Jul 09 '13 at 09:03
  • 1
    No, the type returned by a function is ultimately determined by the return type in the function declaration. If you cast the object just before returning, it will eventually be cast again into the official return type. If that return type is a non-reference you'll get a prvalue; if it is an rvalue-reference, you'll get an xvalue. (But this is equivalent to the behaviour of `std::move`: If the function return type is defined as an lvalue-reference, `std::move` just before returning will not enable move semantics.) – jogojapan Jul 09 '13 at 09:08
  • So, if - for whatever twisted reason - one would like to prevent the implicit move on fn return, one would have to create a second local variable to hold the return and then return that one, right? (`T local1; ... T local2 = local1; /*<- copy*/ ... return local2; /*<- may move*/` – Martin Ba Jul 09 '13 at 09:12
  • @MartinBa But, as you said in the comment, `return local2;` may still trigger a move, right? In what way does this prevent the move? – jogojapan Jul 09 '13 at 09:23
  • `local2`prevents the move of the original, `local1`. I mean, if I were in a situation where I didn't want the local object to be moved-from, I could just copy "its value" into another local object and return that one. That way the original local object won't be returned and thus won't be moved. (And, yes, I really *can't* imagine when I would want to do this.) – Martin Ba Jul 09 '13 at 09:31
  • 1
    @MartinBa Ah, yes, that's right. Of course, `local1` will go out of scope at the `return`, and until then, `local2` has not been moved yet, so indeed.... not much use there. But again, just like `std::move` cannot override the declared return type, my proposed cast cannot do this. But I believe the main point of `std::move` is to guide the compiler in selecting the right function overload (i.e. it is usually applied to function arguments, rather than in return-statements). And in this function the proposed cast is a good candidate for an "anti-move". – jogojapan Jul 09 '13 at 09:38
1
template<class T>
T& unmove(T&& t)
{
    return t;
}

This will change the value category of the argument expression to an lvalue, no matter what it was originally.

void f(const int&); // copy version
void f(int&&); // move version

int main()
{
    int i = ...;

    f(42); // calls move version
    f(move(i)); // calls move version
    f(i); // calls copy version

    f(unmove(42)); // calls copy version
    f(unmove(move(i))); // calls copy version
    f(unmove(i)); // calls copy version
}
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • Of course this fails to address the implicit-move-on-return case that is specifically highlighted in the question – Martin Ba Jul 09 '13 at 12:24
  • @MartinBa: You mean like `return unmove(expr);` for example? It will work there too. – Andrew Tomazos Jul 09 '13 at 12:26
  • yes, `return unmove(local_variable);` – Martin Ba Jul 09 '13 at 12:27
  • @MartinBa: Yes, unmove will work there too and suppress both RVO and move construction of the return value - forcing copy construction. – Andrew Tomazos Jul 09 '13 at 12:28
  • This contradicts what @jogojapan said in the comments to his answer – Martin Ba Jul 09 '13 at 12:29
  • @MartinBa: You are conflating the initialization of a function return value with the use of a function call expression in some other context. These are two different phases. First the return value is initialized by `return expr`, then the function call expression `f(x)` is used by some other context (another enclosing expression, or as the initialization of a variable, etc). Both phases may involve a move/copy construction. For the first phase you can use `return unmove(expr)`, for the second you can use `unmove(f(x))`, to suppress move construction. – Andrew Tomazos Jul 09 '13 at 12:39
  • I don't read it that way. But I guess it would be on me to actually go and try it out (and then come back with a more specific question). – Martin Ba Jul 09 '13 at 12:44
  • @MartinBa: You don't read what what way? Look up function call expressions in the standard (clause 5), and look up the return statement in the standard (clause 6). Trial-and-error is an inefficient and inaccurate way to learn about these things. – Andrew Tomazos Jul 09 '13 at 12:48
  • Reading the standard (trying to understand what it says) is even more inefficient ;-) -- and: *"the type returned by a function is ultimately determined by the return type in the function declaration. If you cast the object just before returning, it will eventually be cast again into the official return type. If that return type is a non-reference you'll get a prvalue; if it is an rvalue-reference, you'll get an xvalue"* implies to me, that `return unmove(local);` does not work. That's what I read that way. – Martin Ba Jul 09 '13 at 12:56
  • @MartinBa: The language is a little sloppy. The expression from a return statement is not "cast" to the return type, it initializes it. Initialization may involve a constructor call depending on the types and value categories involved. – Andrew Tomazos Jul 09 '13 at 13:04
0

The solution to prevent an object to be moved is to make the move constructor of the object private, this way the object can't be moved but can be copied.

Example with move:

enter code here

#include <iostream>

class A {
public:
    std::string s;
    A() : s("test") {}
    A(const A& o) : s(o.s) { std::cout << "move failed!\n";}
    A(A&& o) : s(std::move(o.s)) { std::cout << "move was succesfull!\n"; }
};

int main(int argc, const char * argv[])
{
    A firsObject;
    A secondObject = std::move(firsObject);
    return 0;
}

Example with move disabled:

#include <iostream>

class A {
public:
    std::string s;
    A() : s("test") {}
    A(const A& o) : s(o.s) { std::cout << "move failed!\n";}

private:
    A(A&& o) : s(std::move(o.s)) { std::cout << "move was succesfull!\n"; }
};

int main(int argc, const char * argv[])
{
    A firsObject;
    A secondObject = std::move(firsObject);
    return 0;
}
  • Why have the constructor at all, then? – R. Martinho Fernandes Jul 09 '13 at 09:46
  • @R. Martinho Fernandes, you are right, a move constructor can simply be omitted. In the case above i was thinking of a situation when you need it , this way you can declare a friend class/method that can still use the move constructor if necessary. – Pascalau Razvan Jul 09 '13 at 10:44