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?
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".
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With