Reputation: 21749
I'm watching the "Don’t Help the Compiler" talk by STL, where he has a similar example on slide 26:
struct A
{
A() = default;
A(const A&) { std::cout << "copied" << std::endl; }
A(A&&) { std::cout << "moved" << std::endl; }
};
std::pair<A, A> get_pair()
{
std::pair<A, A> p;
return p;
}
std::tuple<A, A> get_tuple()
{
std::pair<A, A> p;
return p;
}
std::tuple<A, A> get_tuple_moved()
{
std::pair<A, A> p;
return std::move(p);
}
With this, the following call:
get_pair();
get_tuple();
get_tuple_moved();
Produces this output:
moved
moved
copied
copied
moved
moved
Result of get_pair
is move-constructed, which is as expected. A move may also has been completely elided by NRVO, but it is off the topic of the present question.
Result of get_tuple_moved
is also move-constructed, which is explicitly specified to be so. However, result of get_tuple
is copy-constructed, which is completely un-obvious to me.
I thought that any expression passed to return
statement may be thought of as having implicit move
on it, since the compiler knows it is about to go out of scope anyway. Seems like I'm wrong. Can someone clarify, what is going on here?
See also related, but different question: When should std::move be used on a function return value?
Upvotes: 10
Views: 1415
Reputation: 96800
The return statement in get_tuple() should be copy-initialized using the move-constructor, but since the type of the return expression and the return type don't match, the copy-constructor is chosen instead. There was a change made in C++14 where there is now an initial phase of overload resolution that treats the return statement as an rvalue when it is simply an automatic variable declared in the body.
The relevant wording can be found in [class.copy]/p32:
When the criteria for elision of a copy/move operation are met, [..], 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 [..], overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
So in C++14 all output should be coming from the move-constructor of A.
Trunk versions of clang and gcc already implement this change. To get the same behavior in C++11 mode you'll need to use an explicit std::move() in the return statement.
Upvotes: 8