33

I know that in the following situation that the compiler is free to move-construct the return value from makeA (but is also free to elide the copy or move altogether):

struct A
{
    A(A&);
    A(A&&);
};

A makeA()
{
    A localA;
    return localA;
}

What I wonder is whether the compiler is allowed to construct an object of type A from a local object of type B by rvalue reference if it is being constructed in the return statement. In other words, in the following example, is the compiler allowed to select A's constructor 4 for the return value?

struct B { };
struct A {
    A(A&);  // (1)
    A(A&&); // (2)
    A(B&);  // (3)
    A(B&&); // (4)
};

A makeA()
{
    B localB;
    return localB;
}

I ask this because it would seem to me that the same logic that allows a local object of type A to be treated as an rvalue in the return statement should also allow a local of any type to be treated as an rvalue, but I cannot find any examples or questions of this nature.

Niall
  • 30,036
  • 10
  • 99
  • 142
  • 1
    Quick test on clang and g++ shows that both call #3. – T.C. Sep 16 '14 at 18:05
  • `localB` is not an rvalue, so no (well, it can do whatever it wants as long as it follows the "as-if" rule, but it is unlikely to use a move here.) You would need `std::move(localB)`. – juanchopanza Sep 16 '14 at 18:05
  • @juanchopanza Not convinced that this is a duplicate - if you `return localA;` and prevent RVO, you'd select the constructor taking an `A&&`. The question is specific to the return statement. – T.C. Sep 16 '14 at 18:10
  • @T.C. I don't think so. The "try rvalue first" trick is only allowed in situations "When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter" (C++11 12.8/32) – Angew is no longer proud of SO Sep 16 '14 at 18:11
  • I think Jonathan Wakely's answer [here](http://stackoverflow.com/a/17483612/1708801) applies, although it is not really a duplicate per se. – Shafik Yaghmour Sep 16 '14 at 18:12

1 Answers1

32

The rule for this situation changed between 2011 and 2014. The compiler should now treat localB as an rvalue.

The applicable rule for return statements is found in §12.8 [class.copy]/p32, which reads in C++14 (quoting N3936, emphasis mine):

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

The bolded clause was added by CWG issue 1579, expressly to require the converting move constructor A::A(B&&) to be called here. This is implemented in GCC 5 and Clang 3.9.

Back in 2011, this "try rvalue first" rule was closely tied to the criteria for copy elision (quoting N3337):

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

Since copy elision necessarily requires the two to have the same type, this paragraph didn't apply, and the compiler had to use the A::A(B&) constructor.

Note that as CWG 1579 is considered a DR against C++11, compilers should implement its resolution even in C++11 mode.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • why I got A(B&); and A(A&&) ? I compiled the code using -fno-elide-constructors which disable RVO. 1) Isn't it meet the criteria for elision? 2) where does A(A&&) come from? Thanks in advance – camino Mar 06 '15 at 02:33
  • @camino 1) Probably compiler version issues - the `B&&` part is implemented only in GCC trunk. 2) Returning is copy-initialization, so behaves like `A a = b;` - the `B` is first converted to a temporary `A`, and that temporary is moved into the return value. – T.C. Mar 06 '15 at 13:19
  • @Mikhail My use of 2011/2014 rather than C++11/C++14 is intentional. The change was made via a defect report against C++11; such changes are usually considered "de facto" C++11 by compiler writers. – T.C. Apr 18 '15 at 17:51