16

When returning a local object by value, C++ compilers may optimize out unnecessary copies (copy elision) by taking advantage of the move semantics.
"may optimize" implies that if proper conditions are not met, the behavior should fall back to the default return by value semantics, based on copy.
Thus, as I understand it, it is always valid to return a copyable object by value.

But compilers (clang and gcc) do not seem to agree with my interpretation, as shown by the MWE below.

class Foo {
public:
    Foo();
    Foo(const Foo&);
    Foo(Foo&&) = delete;
}

Foo f() { return Foo(); }  // error: call to explicitly deleted constructor of 'Foo'
Foo g() { Foo a; return a; }  // gcc complains, clang is fine
Foo x = g();  // error: call to explicitly deleted constructor of 'A'

Q1: Does return by value requires object to be movable?
Q2: If not, do gcc and clang misbehave on my MWE or am I missing something else?

Boson
  • 270
  • 3
  • 10
  • Because of NRVO/RVO maybe directly call constructor omit move and copy constructor. Although omitted the call, but the move and copy syntax errors can still prevent compilation. – Ron Tang Mar 20 '15 at 11:39
  • 3
    did you really mean to declare the constructors `private`? make them public in `Foo`. – Alexander Oh Mar 20 '15 at 11:50
  • "*... compilers may optimize out unnecessary copies (copy elision) by taking advantage of the move semantics. ...*" -- this is **wrong**. Copy Elison and Move Semantics are two orthogonal concepts. When you have Copy Elison, you have *neither* copy nor move. When you have no Copy Elison, you may get a Copy or a Move operation depending on the situation. – Martin Ba Mar 20 '15 at 14:14
  • And the conditions for the copy or move must be met even if it is elided, so if the copy or move would use a private or deleted constructor, the program is ill-formed. – Jonathan Wakely Mar 20 '15 at 15:15
  • Also see: http://stackoverflow.com/a/26492184/576911 – Howard Hinnant Mar 20 '15 at 15:52

2 Answers2

32

You are simply meeting the intended behaviour of overload resolution: Foo() is an rvalue, so overload resolution finds the constructor Foo(Foo&&) as the best match. Since that overload is deleted, your program is ill-formed. Moreover, there's a special rule that says Foo a; return a; will also perform overload resolution as if a was an rvalue first. (The rule applies essentially whenever the the return statement is eligible for copy elision.)

This is all working as intended. It was you who deleted the overload, so you requested expressly that such constructions be forbidden.

Note that "real" code doesn't usually meet this obstacle, since as soon as you declare a copy constructor, your class will not have any move constructors at all. But you went out of your way to say, "no, actually I do want a move constructor, and I want it to be an error if anyone attempts to use it".

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
8

Regarding this:

Foo g() { Foo a; return a; }  // gcc complains, clang is fine

GCC is right, this shouldn't compile because of [class.copy]/32 (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. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

So implementation should choose move constructor for elision, and as it's deleted, the program is ill-formed.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • 1
    Yes, you say that I want to say.Vote up. – Ron Tang Mar 20 '15 at 11:58
  • I don't think 'Clang' is wrong in the case as the the copy-constructor for 'rvalue' is declared as deleted - not the one for 'lvalues' which is implicitly defined here. In-fact the 'GCC' is wrong to not compile this code. (it shouldn't compile it in case 'Foo(const Foo&) = delete') – AnArrayOfFunctions Mar 20 '15 at 12:23
  • I read it and it states after the text you marked - ' 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.' which means that the constructor for 'lvalue' will be successfully called and this proves mine and 'clang' point again. – AnArrayOfFunctions Mar 20 '15 at 13:36
  • @FISOCPP The overload resolution for rvalue doesn't fail because `Foo` has user-declared move constructor. – Anton Savin Mar 20 '15 at 13:42