Unraveling the Mystery of std::move's Conversion Magic
When invoking std::move(), it's puzzling to observe that the referenced rvalue parameter can be bound to lvalues, which are typically restricted from attaching to rvalue references. Delving into the implementation of std::move() reveals the key to this apparent paradox.
Dissecting the std::move() Implementation
Beginning with the refined version of std::move():
template <typename T> typename remove_reference<T>::type&& move(T&& arg) { return static_cast<typename remove_reference<T>::type&&>(arg); }
Case 1: Invoking move() with Rvalues
When move() is employed with rvalues, such as a temporary object:
Object a = std::move(Object()); // Object() is temporary, hence prvalue
The resulting template instantiation is:
remove_reference<Object>::type&& move(Object&& arg) { return static_cast<remove_reference<Object>::type&&>(arg); }
Since remove_reference transforms T& into T or T&& into T, and Object is a plain value, the final function morphology becomes:
Object&& move(Object&& arg) { return static_cast<Object&&>(arg); }
The cast is crucial since named rvalue references are treated as lvalues.
Case 2: Invoking move() with Lvalues
When move() is invoked with lvalues:
Object a; // a is an lvalue Object b = std::move(a);
The resulting move() instantiation is:
remove_reference<Object&>::type&& move(Object& && arg) { return static_cast<remove_reference<Object&>::type&&>(arg); }
Again, remove_reference translates Object& to Object, yielding:
Object&& move(Object& && arg) { return static_cast<Object&&>(arg); }
The Essence of Reference Collapsing
Understanding Object& && and its ability to bind to lvalues is the key to unlocking the enigma. C 11 introduces special rules for reference collapsing, which dictate:
Object & && = Object & Object & &&& = Object & Object && & = Object & Object && &&& = Object &&
Thus, Object& && actually translates to Object&, a typical lvalue reference that effortlessly binds to lvalues.
The Resultant Function
With these rules in play, the final function becomes:
Object&& move(Object& arg) { return static_cast<Object&&>(arg); }
Mirroring the instantiation for rvalues, it casts its argument to an rvalue reference, thus ensuring uniform behavior.
The Importance of remove_reference
The purpose of remove_reference becomes apparent when examining an alternative function:
template <typename T> T&& wanna_be_move(T&& arg) { return static_cast<T&&>(arg); }
When instantiated with an lvalue:
Object& && wanna_be_move(Object& && arg) { return static_cast<Object& &&&>(arg); }
applying reference collapsing rules reveals an unusable move-like function, returning lvalues for lvalue arguments. The culprit is the absence of remove_reference, which hinders the proper conversion to rvalue references.
The above is the detailed content of How does std::move() bind to lvalues when rvalue references are supposed to only bind to rvalues?. For more information, please follow other related articles on the PHP Chinese website!