12

In the code below, why doesn't the first call mkme = mvme_rv dispatch to T& operator=(const T&&)?

#include <iostream>
#include <string>
#include <vector>

using namespace std;
using T = vector<int>;

int main()
{
  T mvme(10, 1), mkme;
  T&& mvme_rv = move(mvme); // rvalue ref?
  mkme = mvme_rv;           // calls T& operator=(const T&)?
  cout << mvme.empty();     // 0
  mkme = move(mvme_rv);     // calls T& operator=(const T&&)?
  cout << mvme.empty();     // 1
}
PSL
  • 258
  • 1
  • 9
  • 3
    The type of `mvme_rv` is rvalue reference to `T`, but when you uses that variable through its name it's nothing more than an lvalue. – skypjack Sep 08 '16 at 08:49
  • 2
    Distinguish "rvalue reference" and "rvalue". –  Sep 08 '16 at 08:49
  • 1
    Naming expressions are always lvlaues. The word "rvalue" in the term "rvalue reference" describes the kind of *reference*: An rvalue reference is a reference that binds to rvalues, and an lvalue reference is a reference that binds to lvalues (mostly). In that sense, rvalue references are a new language feature that adds a generic rvalue-to-lvalue conversion to the language, which didn't exist hitherto. – Kerrek SB Sep 08 '16 at 08:52
  • `T && t = move(mvme); T && t2 = t; // ERROR!!!` If you can understand why this is wrong, I think you can solve your question on your own. – Yves Sep 08 '16 at 09:07

2 Answers2

17

As skypjack correctly comments, accessing an object through its name always results in an lvalue reference.

This is a safety feature and if you think it through you will realise that you are glad of it.

As you know, std::move simply casts an l-value reference to an r-value reference. If we use the returned r-value reference immediately (i.e. un-named) then it remains an r-value reference.

This means that the use of the r-value can only be at the point in the code where move(x) is mentioned. From a code-reader's perspective, it's now easy to see where x's state became undefined.

so:

 1: auto x = make_x();
 2: auto&& r = std::move(x);
 3: // lots of other stuff
35: // ...
54: // ...
55: take_my_x(r);

does not work. If it did, someone maintaining the code would have a hard time seeing (and remembering) that x (defined on line 1) enters an undefined state on line 55 through a reference taken on line 2.

This is a good deal more explicit:

 1: auto x = make_x();
 2: //
 3: // lots of other stuff
35: // ...
54: // ...
55: take_my_x(std::move(x));
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
-3

This line of code:

mkme = mvme_rv;

is a copy and will thus use a copy assignment (T& operator=(const T&)). The key thing about this is BOTH objects can be used afterwards and should - if implemented correctly - provide two identical objects.

By contrast, this line of code:

mkme = move(mvme_rv);

is a move assignment (T& operator=(const T&&)). By convention, this will trash the mvme_rv object (or at least clear it) and make mkme basically what mvme_rv was previously.

Effectively T&& means a temporary object (aka xvalue) - something that will not last. The std::move method basically casts the object to a temporary (credit to @richard-Hodges for that wording). This can then be used in the move assignment method.

So finally to answer you question of why doesn't mkme = mvme_rv dispatch to T& operator=(const T&&): it's because mvme_rv isn't a temporary object (aka xavalue).


More about xvalues: http://en.cppreference.com/w/cpp/language/value_category

thab
  • 666
  • 1
  • 6
  • 14
  • "std::move operator basically takes a normal object and rips out the guts" No, it doesn't affect the operand. And it isn't an operator. – juanchopanza Sep 08 '16 at 10:02