Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does std::move() transfer values into RValues?

I just found myself not fully understanding the logic of std::move().

At first, I googled it but seems like there are only documents about how to use std::move(), not how its structure works.

I mean, I know what the template member function is but when I look into std::move() definition in VS2010, it is still confusing.

the definition of std::move() goes below.

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);     } 

What is weird first to me is the parameter, (_Ty&& _Arg), because when I call the function like you see below,

// main() Object obj1; Object obj2 = std::move(obj1); 

it basically equals to

// std::move() _Ty&& _Arg = Obj1; 

But as you already know, you can not directly link a LValue to a RValue reference, which makes me think that it should be like this.

_Ty&& _Arg = (Object&&)obj1; 

However, this is absurd because std::move() must work for all the values.

So I guess to fully understand how this works, I should take a look at these structs too.

template<class _Ty> struct _Remove_reference {   // remove reference     typedef _Ty _Type; };  template<class _Ty> struct _Remove_reference<_Ty&> {   // remove reference     typedef _Ty _Type; };  template<class _Ty> struct _Remove_reference<_Ty&&> {   // remove rvalue reference     typedef _Ty _Type; }; 

Unfortunately it's still as confusing and I don't get it.

I know that this is all because of my lack of basic syntax skills about C++. I'd like to know how these work thoroughly and any documents that I can get on the internet will be more than welcomed. (If you can just explain this, that will be awesome too).

like image 341
Dean Seo Avatar asked Sep 22 '11 06:09

Dean Seo


People also ask

What does std :: move () do?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

How does std move work C++?

In C++11, std::move is a standard library function that casts (using static_cast) its argument into an r-value reference, so that move semantics can be invoked. Thus, we can use std::move to cast an l-value into a type that will prefer being moved over being copied.

Does std :: move make a copy?

std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.

Is std :: string movable?

Yes, std::string (since C++11) is able to be moved i.e. it supports move semantics.


2 Answers

We start with the move function (which I cleaned up a little bit):

template <typename T> typename remove_reference<T>::type&& move(T&& arg) {   return static_cast<typename remove_reference<T>::type&&>(arg); } 

Let's start with the easier part - that is, when the function is called with rvalue:

Object a = std::move(Object()); // Object() is temporary, which is prvalue 

and our move template gets instantiated as follows:

// move with [T = Object]: remove_reference<Object>::type&& move(Object&& arg) {   return static_cast<remove_reference<Object>::type&&>(arg); } 

Since remove_reference converts T& to T or T&& to T, and Object is not reference, our final function is:

Object&& move(Object&& arg) {   return static_cast<Object&&>(arg); } 

Now, you might wonder: do we even need the cast? The answer is: yes, we do. The reason is simple; named rvalue reference is treated as lvalue (and implicit conversion from lvalue to rvalue reference is forbidden by standard).


Here's what happens when we call move with lvalue:

Object a; // a is lvalue Object b = std::move(a); 

and corresponding move instantiation:

// move with [T = Object&] remove_reference<Object&>::type&& move(Object& && arg) {   return static_cast<remove_reference<Object&>::type&&>(arg); } 

Again, remove_reference converts Object& to Object and we get:

Object&& move(Object& && arg) {   return static_cast<Object&&>(arg); } 

Now we get to the tricky part: what does Object& && even mean and how can it bind to lvalue?

To allow perfect forwarding, C++11 standard provides special rules for reference collapsing, which are as follows:

Object &  &  = Object & Object &  && = Object & Object && &  = Object & Object && && = Object && 

As you can see, under these rules Object& && actually means Object&, which is plain lvalue reference that allows binding lvalues.

Final function is thus:

Object&& move(Object& arg) {   return static_cast<Object&&>(arg); } 

which is not unlike the previous instantiation with rvalue - they both cast its argument to rvalue reference and then return it. The difference is that first instantiation can be used with rvalues only, while the second one works with lvalues.


To explain why do we need remove_reference a bit more, let's try this function

template <typename T> T&& wanna_be_move(T&& arg) {   return static_cast<T&&>(arg); } 

and instantiate it with lvalue.

// wanna_be_move [with T = Object&] Object& && wanna_be_move(Object& && arg) {   return static_cast<Object& &&>(arg); } 

Applying the reference collapsing rules mentioned above, you can see we get function that is unusable as move (to put it simply, you call it with lvalue, you get lvalue back). If anything, this function is the identity function.

Object& wanna_be_move(Object& arg) {   return static_cast<Object&>(arg); } 
like image 107
Vitus Avatar answered Oct 08 '22 19:10

Vitus


_Ty is a template parameter, and in this situation

Object obj1; Object obj2 = std::move(obj1); 

_Ty is type "Object &"

which is why the _Remove_reference is necessary.

It would be more like

typedef Object& ObjectRef; Object obj1; ObjectRef&& obj1_ref = obj1; Object&& obj2 = (Object&&)obj1_ref; 

If we didn't remove the reference it would be like we were doing

Object&& obj2 = (ObjectRef&&)obj1_ref; 

But ObjectRef&& reduces to Object &, which we couldn't bind to obj2.

The reason it reduces this way is to support perfect forwarding. See this paper.

like image 28
Vaughn Cato Avatar answered Oct 08 '22 19:10

Vaughn Cato