Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what's the magic of std:move

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

like image 703
Chang Avatar asked Aug 30 '12 08:08

Chang


3 Answers

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.

like image 79
interjay Avatar answered Oct 19 '22 23:10

interjay


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.

like image 42
Kerrek SB Avatar answered Oct 20 '22 00:10

Kerrek SB


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).

like image 24
Andrzej Avatar answered Oct 20 '22 00:10

Andrzej