简体   繁体   中英

How is the move constructor of member variable invoked without using std::forward?

An example here for std::forward ,

// forward example
#include <utility>      // std::forward
#include <iostream>     // std::cout

// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}

// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
  overloaded (x);                   // always an lvalue
  overloaded (std::forward<T>(x));  // rvalue if argument is rvalue
}

int main () {
  int a;

  std::cout << "calling fn with lvalue: ";
  fn (a);
  std::cout << '\n';

  std::cout << "calling fn with rvalue: ";
  fn (0);
  std::cout << '\n';

  return 0;
}

Output:
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]

mentions that

the fact that all named values (such as function parameters) always evaluate as lvalues (even those declared as rvalue references)

Whereas, the typical move constructor looks like

ClassName(ClassName&& other)
   : _data(other._data)
{
}

which looks like _data(other._data) should invoke the move constructor of _data 's class. But, how is it possible without using std::forward ? In other words, shouldn't it be

ClassName(ClassName&& other)
   : _data(std::forward(other._data))
{
}

?

Because, as pointed out in std:forward case,

all then named values should evaluate as lvalue

I more and more like C++ because of the depth of issue like this and the fact that the language is bold enough to provide such features :) Thank you!

A typical move constructor looks like this (assuming it is explicitly implemented: you might want to prefer = default ):

ClassName::ClassName(ClassName&& other)
    : _data(std::move(other._data)) {
}

Without the std::move() the member is copied: since it has a name other is an lvalue. The object the reference is bound to is an rvalue or an object considered as such, however.

std::forward<T>(obj) is always used with an explicit template argument. In practice the type is that deduced for a forwarding reference . These look remarkably like rvalue references but are something entirely different! In particular, a forwarding reference may refer to an lvalue.

You may be interested in my Two Daemons article which describes the difference in detail.

This Ideone example should make things pretty clear for you. If not, keep reading.

The following constructor accepts Rvalues only. However, since the argument "other" got a name it lost its "rvalueness" and now is a Lvalue. To cast it back to Rvalue, you have to use std::move . There's no reason to use std::forward here because this constructor does not accept Lvalues. If you try to call it with a Lvalue, you will get compile error.

ClassName(ClassName&& other)
     : _data(std::move(other._data))
{
    // If you don't use move, you could have: 
    //    cout << other._data;
    // And you will notice "other" has not been moved.    
}

The following constructor accepts both Lvalues and Rvalues. Scott Meyers called it "Universal Rerefences", but now it's called "Forwarding References". That's why, here, it's a must to use std::forward so that if other was an Rvalue, _data constructor will get called with an Rvalue. If other was an Lvalue, _data will be constructed with an Lvalue. That's why it's called perfect-forwarding.

template<typename T>
ClassName(T&& other)
   : _data(std::forward<decltype(_data)>(other._data))
{
}

I've tried to use your constructors as an example so you could understand, but this is not specific to constructors. This applies to functions as well.

With the first example tho, since your first constructor only accepts Rvalues, you could perfectly use std::forward instead, and both would do the same thing. But it's best not to do it, because people may think that your constructor accepts a forwarding reference , when it actually doesn't.

std::forward should be used with a forwarding reference .
std::move should be used with an rvalue reference .

There is nothing particular about constructors. The rules apply the same to any function, member function or constructor.

The most important thing is to realize when you have a forwarding reference and when you have an rvalue reference . They look similar but are not.

A forwarding reference is always in the form:

T&& ref

for T some deduced type .

For instance, this is a forwarding reference:

template <class T>
auto foo(T&& ref) -> void;

All these are rvalue references:

auto foo(int&& ref) -> void; // int not deduced

template <class T>
auto foo(const T&& ref); // not in form `T&&` (note the const)

template <class T>
auto foo(std::vector<T>&& ref) -> void; // not in form `T&&`

template <class T>
struct X {
    auto foo(T&& ref) -> T; // T not deduced. (It was deduced at class level)
};

For more please check this excellent in-depth article by Scott Meyers with the note that when the article was written the term "universal reference" was used (actually introduced by Scott himself). Now it is agreed that "forwarding reference" better describes it's purpose and usage.


So your example should be:

ClassName(ClassName&& other)
   : _data(std::move(other._data))
{
}

as other is an rvalue reference because ClassName is not a deduced type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM