7

The following codes make VC2010 fail :

//code1
std::string& test1(std::string&& x){
  return x;
}
std::string str("xxx");
test1(str);  //#1 You cannot bind an lvalue to an rvalue reference

//code2 
std::string&& test1(std::string&& x){
  return x;  //#2 You cannot bind an lvalue to an rvalue reference
}

There are some articles to explain #1, but I don't understand why #2 also fails.

let's see how std::move implements

template<class _Ty> inline
    typename tr1::_Remove_reference<_Ty>::_Type&&
        move(_Ty&& _Arg)
    {   // forward _Arg as movable
    return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
    }
  1. The argument of move is still a rvalue reference,but move(str) is ok!
  2. move also returns rvalue.

What's the magic of std:move?

Thanks

Chang
  • 3,953
  • 2
  • 30
  • 43
  • 2
    In the second case, `x` is an _rvalue reference_, it's not an rvalue itself (because it's got a name). – jogojapan Aug 30 '12 at 08:51
  • 2
    http://stackoverflow.com/questions/5529881/move-semantics-stdmove-how-use-it http://stackoverflow.com/questions/7510182/how-does-stdmove-transfer-values-into-rvalues – Mr.Anubis Aug 30 '12 at 08:56

5 Answers5

10

std::move's parameter looks like it is an rvalue reference, which does seem confusing - why can you call move(str), when str is not an rvalue?

The trick here is that rvalue references work confusingly on template parameters:

If template parameter T is int, then T&& will be an rvalue reference int&&.
But if T is an lvalue refernce int&, then T&& will also be lvalue reference int&.

This is because of the way & and && combine:

Type & &&   ==  Type &
Type && &   ==  Type &
Type & &    ==  Type &
Type && &&  ==  Type &&

So when you call move(str), T is std::string&, and the parameter type of move<std::string&> is also std::string& - an lvalue reference, which allows the function call to compile. Then all move has to do is cast the value to an rvalue reference.

interjay
  • 107,303
  • 21
  • 270
  • 254
  • 3
    +1 Ah, you spotted the OP's confusion much better. The type of `move`'s argument is indeed not an rvalue reference, but a *universal reference*. – Kerrek SB Aug 30 '12 at 09:42
4

You can think of std::move as just a cast (but an expression cast, not a type cast). The expression std::move(x) is an rvalue with the same value as x, but it works even if x itself is an lvalue.

In your example "code2", x is indeed an lvalue (of type "rvalue reference to string"). This lvalue cannot bind to the function's return type ("rvalue reference to string"), so you need to cast it explicitly to an rvalue expression.

We can also make the opposite of move, which I usually call stay, which turns rvalues into lvalues (use with care!):

template <typename T> T & stay(T && t) { return t; }

This is primarily useful for perverse one-liners, or to impress girls at a bar.

Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
3

Folks here already answered the question, but I feel it needs to be said more explicitly. The reason people are often confused with rvalue references is in the rule:

A named rvalue reference is an lvalue.

Confusing at first, this rule makes sense. The goal of an rvalue reference is to bind to object you will no longer need: either to a temporary or to something you know you will never need, but the compiler would not be able to figure it out.

A named rvalue reference is something you can refer to a number of times:

std::unique_ptr<int> && rref = std::unique_ptr<int>{ new int{1} };
std::unique_ptr<int> p2{rref};  // if it worked...
rref->use();                    // this would crash 

Here, I have created a temporary in the first line but due to the binding to rvalue reference, I made it work almost like an automatic object: I can access it multiple times. And in order for the last line to work, the second line must not compile.

What std::move does is to change a named rvalue reference (an lvalue) to an unnamed rvalue reference (an rvalue).

Andrzej
  • 5,027
  • 27
  • 36
0

There are some articles to explain #1, but I don't understand why #2 also fails.

Remember, if it has a name, it's an lvalue. In your second case, x is still an lvalue even if it's an rvalue reference. std::move has the ability to turn lvalues into rvalues by doing nothing more than a static_cast<Type&&>(yourVar) on them. The resulting expression is an rvalue that can be accepted by any code requesting an rvalue reference. (a Type &&)

I'll illustrate with a few examples. In your original example, replace:

std::string str("xxx");
test1(str);

with

test1(string("xxx"));

There, the string object no longer has a name, it is now an rvalue and is accepted by test1.

How does move work? Again, quite simple. Again, replace your call with:

test1(std::move(str));

or with

test1(static_cast<std::string&&>(str));

Same thing basically, just that std::move better explains the intention.

Takeaways

  • if it has a name it's an lvalue

  • if it doesn't have a name, it's an rvalue and can be accepted wherever a Type && (an rvalue reference) is asked for.

  • You can static_cast an lvalue to an rvalue (but use std::move instead, that's what it's for), but you need to know what you're doing when you're doing that. (that is, use it on types that have move semantics implemented, I won't go into any detail here, I'll just link to a great article on the move semantics topic)

EddieBytes
  • 1,333
  • 9
  • 20
0

The magic of move semantics is to exploit destructibility.

Imagine a class that manages the lifetime of a dynamically allocated buffer like std::string: A copy constructor taking string const& cannot know whether the argument is actually needed afterwards and thus always has to clone this buffer, which can be an expensive operation.

If we can (by overloading the copy constructor for string&&) know that the argument is disposable, we can just "steal" the buffer not having to copy (over and over for complex expressions).

As noted earlier, an rvalue reference is an lvalue (it allowed us full access in order to steal the buffer in the above example). Now suppose we wanted to use the argument in another, similary overloaded call expression: Without std::move the argument seems precious to us and the string const& overload is resolved while std::move allows us to repeat the "move trick" (because we know the argument came from an rvalue at the call site).

Dude
  • 583
  • 2
  • 9