Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why explicit std::move is needed when returning compatible type?

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?

like image 269
Mikhail Avatar asked Apr 18 '15 12:04

Mikhail


People also ask

Why is std :: move necessary?

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.

Do you need to return std :: move?

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.

Why do we need move constructors?

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.

What does C++ std :: move do?

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.


1 Answers

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.

like image 183
David G Avatar answered Nov 16 '22 03:11

David G