7

I am learning c++11 and i have a question regarding move semantics and rvalue references. My sample code is as following (C++ Shell URL is cpp.sh/8gt):

#include <iostream>
#include <vector>

void aaa(std::vector<int>&& a)
{
    std::cout << "size of a before move: " << a.size() << std::endl;
    std::vector<int> v;
    v = a; /*std::move(a)*/
    std::cout << "size of a after move: " << a.size() << std::endl;
}

int main ()
{
  std::vector<int> foo (3,0);

  aaa(std::move(foo));

  return 0;
}

The result of the is:

size of a before move: 3  
size of a after move: 3

It seems the move assign operator of std::vector is not invoked at line v = a in function aaa, otherwise a would have size 0 instead of 3.
However if i change v = a to v = std::move(a) the output became

size of a before move: 3
size of a after move: 0

and I thinke the move assign operator of std::vector has been invoked this time.

My quesiton is why the assign operator is not invoked the first time? According to c++ reference std::vector has a assign operator which takes a rvalue reference.

copy (1) vector& operator= (const vector& x);
move (2) vector& operator= (vector&& x);
initializer list (3) vector& operator= (initializer_list il);

Then at line v = a, since a is declared as rvalue reference, the move operator should be invoked. Why we still need to wrap a with a std::move?

Many thanks in advance!

[edit] I think both Loopunroller and Kerrek SB answered my question. Thanks! I don't think I can choose two answers so I will just pick the first one.

Song
  • 397
  • 1
  • 5
  • 9
  • 2
    An expression referring to an rvalue reference *variable* is an lvalue, since you can refer to the variable multiple times. – dyp Oct 23 '14 at 16:34
  • 2
    Because "rvalue reference" is a bad and confusing name. And because C++ is bad and confusing. – Lightness Races in Orbit Oct 23 '14 at 16:37
  • 2
    This is, of course, a good thing (in a bad and confusing way). Otherwise, `v = a` would silently modify `a`, causing exactly the kind of `auto_ptr`-style weirdness that move semantics were intended to avoid. – Mike Seymour Oct 23 '14 at 16:46

2 Answers2

11

This note from [expr]/6 might clarify what is going on (emphasis mine):

[ Note: An expression is an xvalue if it is:

  • the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type,
  • a cast to an rvalue reference to object type,
  • a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or
  • a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.

In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not. — end note ]

It is not hard to see that the expression std::move(a) is an xvalue according to the list above (bullet one).
a is the name of an rvalue reference, and thus an lvalue as an expression.

Columbo
  • 60,038
  • 8
  • 155
  • 203
9

The expression a is an lvalue. The expression std::move(a) is an rvalue. Only rvalues bind to rvalue references, which make up the move constructor and move assignment operator.

It is worth repeating this to yourself until it makes sense: Evaluating any reference variable, and also dereferencing any dereferenceable pointer, produces an lvalue.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 3
    An important point is why this rule is as it is. When he invokes `a = v`, the compiler has no way of knowing that he won't need `v` again (and in fact, he does use it later), so it cannot risk changing the value unless you specifically authorize it to. – James Kanze Oct 23 '14 at 17:17
  • @JamesKanze: That's right. As a golden rule, rvalues should guarantee to not be aliased. This isn't enforced or enforceable at the language level (other languages like Rust do enforce this), but it should be how one thinks about rvalues. – Kerrek SB Oct 23 '14 at 18:30
  • Another way to remember this is that an *rvalue reference* is talking about what can be *bound* to the reference: an rvalue. It doesn't mean that using the reference is the same as using an rvalue. – M.M Oct 23 '14 at 23:27
  • @KerrekSB The fundamental idea is that an rvalue reference binds to a "throw-away" object; an object that won't be used again. If the object is a temporary (an rvalue), or is being returned from a function, it's automatically a throw-away object, since there's no way it can be used again. Otherwise, you have to tell the compiler that you're not using the object again. The way to do that is with `std::move`. – James Kanze Oct 24 '14 at 08:19