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

时间:2016-02-12 20:38:40

标签: c++ c++11 move-semantics rvalue move-constructor

An example here for students[0],

std::forward

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

// 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]

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

std::forward

?

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!

3 个答案:

答案 0 :(得分:3)

A typical move constructor looks like this (assuming it is explicitly implemented: you might want to prefer SELECT * FROM cat WHERE id LIKE '11%'; ## get all cats ):

= default

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

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

答案 1 :(得分:1)

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 <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>. There's no reason to use std::move here because this constructor does not accept Lvalues. If you try to call it with a Lvalue, you will get compile error.

std::forward

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 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. } 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 std::forward

perfect-forwarding.

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 template<typename T> ClassName(T&& other) : _data(std::forward<decltype(_data)>(other._data)) { } 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 std::forward, when it actually doesn't.

答案 2 :(得分:1)

blobAsUUID() should be used with a std::forward.
forwarding reference should be used with an std::move.

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 rvalue reference and when you have an forwarding reference. They look similar but are not.

A forwarding reference is always in the form:

rvalue reference

for T&& ref some deduced type.

For instance, this is a forwarding reference:

T

All these are rvalue references:

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

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:

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

as ClassName(ClassName&& other) : _data(std::move(other._data)) { } is an other because rvalue reference is not a deduced type.