The correct answer depends on which C++ standard you are talking about.
If we are talking about C++11, clang is correct (an explicit move is needed). If we are talking about C++14, gcc is correct (an explicit move is not needed).
C++11 says in N3290/[class.copy]/p32:
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. If overload resolution
fails, ...
This demands that you only get the implicit move when the return expression has the same type as the function return type.
But CWG 1579 changed this, and this defect report was accepted after C++11, and in time for C++14. This same paragraph now reads:
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, ...
This modification basically allows the return expression type to be convertible-to the function return type and still be eligible for implicit move.
Does this mean that the code needs a #if
/#else
based on the value of __cplusplus
?
One could do that, but I wouldn't bother. If I were targeting C++14, I would just:
return i;
If the code is unexpectedly run under a C++11 compiler, you will be notified at compile-time of the error, and it is trivial to fix:
return std::move(i);
If you are just targeting C++11, use the move
.
If you want to target both C++11 and C++14 (and beyond), use the move
. The downside of using move
gratuitously is that you can inhibit RVO (Return Value Optimization). However, in this case, RVO is not even legal (because of the conversion from the return
statement to the return type of the function). And so the gratuitous move
does not hurt anything.
The one time you might lean towards a gratuitous move
even when targeting C++14 is if without it, things still compile in C++11, and invoke an expensive copy conversion, as opposed to a move conversion. In this case, accidentally compiling under C++11 would introduce a silent performance bug. And when compiled under C++14 the gratuitous move
still has no detrimental effects.