Home  >  Article  >  Backend Development  >  How does std::move() bind to lvalues when rvalue references are supposed to only bind to rvalues?

How does std::move() bind to lvalues when rvalue references are supposed to only bind to rvalues?

DDD
DDDOriginal
2024-11-14 10:46:01963browse

How does std::move() bind to lvalues when rvalue references are supposed to only bind to rvalues?

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!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn