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
See MCVE in action.
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?
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object.
When returning a named local variable or a temporary expression directly, you should avoid the explicit std::move . The compiler must (and will in the future) move automatically in those cases, and adding std::move might affect other optimizations.
A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying. For more information about move semantics, see Rvalue Reference Declarator: &&. This topic builds upon the following C++ class, MemoryBlock , which manages a memory buffer.
std::move() is a function used to convert an lvalue reference into the rvalue reference. Used to move the resources from a source object i.e. for efficient transfer of resources from one object to another. std::move() is defined in the <utility> header.
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.
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