0

They say that members of Rvalues are also Rvalues - which makes a lot of sense. So this is either a VC++-specific bug or a bug in my understanding of Rvalues.

Take this toy code:

#include <vector>
#include <iostream>
using namespace std;

struct MyTypeInner
{
    MyTypeInner()   {};
    ~MyTypeInner()                     { cout << "mt2 dtor" << endl; }
    MyTypeInner(const MyTypeInner& other)  { cout << "mt2 copy ctor" << endl; }
    MyTypeInner(MyTypeInner&& other)       { cout << "mt2 move ctor" << endl; }
    const MyTypeInner& operator = (const MyTypeInner& other)
    {
        cout << "mt2 copy =" << endl;       return *this;
    }
    const MyTypeInner& operator = (MyTypeInner&& other)
    {
        cout << "mt2 move =" << endl;       return *this;
    }
};

struct MyTypeOuter
{
    MyTypeInner mt2;

    MyTypeOuter()   {};
    ~MyTypeOuter()                     { cout << "mt1 dtor" << endl; }
    MyTypeOuter(const MyTypeOuter& other)  { cout << "mt1 copy ctor" << endl;  mt2 = other.mt2; }
    MyTypeOuter(MyTypeOuter&& other)       { cout << "mt1 move ctor" << endl;  mt2 = other.mt2; }
    const MyTypeOuter& operator = (const MyTypeOuter& other)    
    {
        cout << "mt1 copy =" << endl;       mt2 = other.mt2;   return *this;
    }
    const MyTypeOuter& operator = (MyTypeOuter&& other)
    {
        cout << "mt1 move =" << endl;   mt2 = other.mt2;    return *this;
    }
};

MyTypeOuter func()   {  MyTypeOuter mt; return mt; }

int _tmain()
{
    MyTypeOuter mt = func();
    return 0;
}

This code outputs:

mt1 move ctor

mt2 copy =

mt1 dtor

mt2 dtor

That is, MyTypeOuter's move ctor calls MyTypeInner's copy, not move. If I modify the code to:

MyTypeOuter(MyTypeOuter&& other)       
{ cout << "mt1 move ctor" << endl;  mt2 = std::move(other.mt2); }

The output is as expected:

mt1 move ctor

mt2 move =

mt1 dtor

mt2 dtor

It seems VC++ (both 2010 and 2013) do not respect this part of the standard. Or am I missing something?

Community
  • 1
  • 1
Ofek Shilon
  • 14,734
  • 5
  • 67
  • 101
  • Did you compile this with full optimizations? – RedX Aug 06 '14 at 06:19
  • No. With optimizations RVO kicks in and the entire move semantics logic is mincemeat. – Ofek Shilon Aug 06 '14 at 06:19
  • You still have to `std::move(other)` in the constructors, the compiler will only treat the members as rvalues without a move if you were to do something like `MyTypeInner mt = func().mt2;` – user657267 Aug 06 '14 at 06:29
  • So in what way are rvalue-members rvalues by themselves? I would expect this definition to manifest exactly here. In the statement mt2 = other.mt2, if the rhs is indeed an rvalue a *move* assignment should be called. – Ofek Shilon Aug 06 '14 at 06:31
  • Could you explain what you mean by an "rvalue menmber"? – juanchopanza Aug 06 '14 at 06:31
  • @juanchopanza, I mean it as a member of an rvalue. In this context - a member of an argument which is an rvalue. – Ofek Shilon Aug 06 '14 at 06:33
  • 4
    @OfekShilon `other` in that expression is an lvalue unless you move it, see [this](http://stackoverflow.com/questions/14486367/in-c11-why-use-stdmove-when-you-have) question. – user657267 Aug 06 '14 at 06:36
  • @OfekShilon I see. That is a difficult question because the type could have, say, lvalue references as members. But in your case, as others pointed out, `other` is an lvalue. I had added an answer about this. – juanchopanza Aug 06 '14 at 06:43
  • @OfekShilon rvalues and lvalues are *expressions*; I think by "member of an rvalue" you mean "member of a temporary object"? In this case `other` is an lvalue expression even though it may (or may not!) refer to a temporary object. – M.M Aug 06 '14 at 06:44

1 Answers1

3

Whether the member of an rvalue is an rvalue or not is not the issue here, because you are dealing with lvalues inside your assignment operator.

In this move assignment operator,

const MyTypeOuter& operator = (MyTypeOuter&& other)
{
    cout << "mt1 move =" << endl;
    mt2 = other.mt2;
    return *this;
}

other is an lvalue (since it has a name), and by extension so is other.mt2. When you say mt2 = other.mt2 you can only invoke the standard assignment operator.

In order to invoke the move constructor, you need to make other.mt2 look like an rvalue, and this is what std::move achieves:

const MyTypeOuter& operator = (MyTypeOuter&& other)
{
    cout << "mt1 move =" << endl;   
    mt2 = std::move(other.mt2);
    return *this;
}

See this related question.

Community
  • 1
  • 1
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • 2
    I think what OP was trying to get at was `std::move(other).mt2` rather than `std::move(other.mt2);`, both result in the move constructor being called but the former shows how members of rvalues are rvalues themselves (and which incidentally shows how the popular definition of lvalues doesn't always hold, seeing as `mt2` has a name and yet it isn't an lvalue in the first case). – user657267 Aug 06 '14 at 06:43
  • I see now, thanks a lot. That still seems like a weird language design choice - it now forces me to add specialized move ctors/assignments for every type in my code, as opposed to enjoying for free the benefits of library types with move semantics. But never mind (I'm sure going there would byte us in 1000 places I can't imagine). – Ofek Shilon Aug 06 '14 at 06:50
  • @user657267 I couldn't reach that interpretation of the question. Particularly, I see no mention of `std::move(other).mt2`. – juanchopanza Aug 06 '14 at 06:57
  • @OfekShilon Most of the time, the compiler generated special functions do the right thing, so you don't have that much work to do. You need to know the rules about when you get a free move copy ctor and move assignment operator though (or, more realistically, know where to look them up.) – juanchopanza Aug 06 '14 at 06:58