9

Lets take two structs/classes

struct C1{
  C1(){};
  C1(C1&){std::cout<<"copy"<<std::endl;}
  C1(C1&&){std::cout<<"move"<<std::endl;}};

struct C2{
  C1 c;
  C2(){};
  C1 get1(){return c;}
  C1 get2(){return std::move(c);}};

And than

C1 a1=C2().c;
C1 a2=C2().get1();
C1 a3=C2().get2();

the output is

move
copy
move

Question

We know that members of rvalues are rvalues themselves. This is why with a1, the move constructor is called. Why than, is the copy constructor called in the case of a2. We are returning an rvalue from a function.

To put it differently, std::move casts to an rvalue. But, as a member of an rvalue, c, is already an rvalue. Why than is there a difference between the behavior with a2 and a3?

Guillaume Racicot
  • 32,627
  • 7
  • 60
  • 103
  • Your copy-constructor is not callable with rvalues at all. Whenever you see it called, it is not to copy an rvalue, but for some other reason. – AnT Jan 29 '18 at 06:22

2 Answers2

7

Good question. The dull answer is that there's just no no rule in the C++ spec that says that returning a member from a dying object like this automatically moves it.

You may be interested in what's called rvalue reference to this. It let's you overload on && and & such that you can manually implement the behaviour you expected:

struct C2{
  C1 c;
  C2(){};
  C1 get1() & { std::cout << "&" << std::endl; return c;}
  C1 get1() && { std::cout << "&&" << std::endl; return std::move(c);}
  C1 get2(){return std::move(c);}
};

Now

C1 a2=C2().get1();

prints

&&
move

Cool, but quite rare.

Related:

Johan Lundberg
  • 23,281
  • 9
  • 67
  • 91
  • 1
    Perhaps there should be such a rule. Time to write a standards proposal? – Jesper Juhl Jan 28 '18 at 19:46
  • Thank you for your answer, it has put me at ease. I thought that I had missed something crucial, but it was just a missing rule, as you stated. I know about the possibility of the overload, I simply tried to provide the smallest example of the behavior; rvalue (member of an rvalue) being returned by a copy. Thank you :) – LeastSquaresWonderer Jan 28 '18 at 19:51
2

We know that members of rvalues are rvalues themselves.

Yes this is true, as states [expr.ref]/4.2 (emphasis mine):

If E2 is a non-static data member and the type of E1 is “cq1 vq1 X”, and the type of E2 is “cq2 vq2 T”, the expression designates the named member of the object designated by the first expression. If E1 is an lvalue, then E1.E2 is an lvalue; otherwise E1.E2 is an xvalue. Let the notation vq12 stand for the “union” of vq1 and vq2; that is, if vq1 or vq2 is volatile, then vq12 is volatile. Similarly, let the notation cq12 stand for the “union” of cq1 and cq2; that is, if cq1 or cq2 is const, then cq12 is const. If E2 is declared to be a mutable member, then the type of E1.E2 is “vq12 T”. If E2 is not declared to be a mutable member, then the type of E1.E2 is “cq12 vq12 T”.

And also, from [expr.ref]/4.5:

If E2 is a member enumerator and the type of E2 is T, the expression E1.E2 is a prvalue. The type of E1.E2 is T.

So far so good. You can only get an lvalue if E1 is an lvalue itself, otherwise it's an xvalue or a prvalue.

But, as a member of an rvalue, c, is already an rvalue.

This is where your assumptions are wrong.

From [class.this]/1 (emphasis mine)

In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*.

this is a prvalue of type X*, and dereferencing a pointer of type X yield an lvalue of type X.

Since accessing a member inside a member function is equivalent to (*this).m, then m is accessed through an lvalue of type X.

So your code is equivalent to:

C1 get1() { return (*this).c; }
//      lvalue ----^       ^--- must be an lvalue too then.

Since this is always the same type, then even when using a function ref-qualifier the expression c inside a member function will always be an lvalue:

C1 get1() && { return (*this).c; }
//                            ^---- lvalue again, accessing through a pointer
Guillaume Racicot
  • 32,627
  • 7
  • 60
  • 103