10

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?

Community
  • 1
  • 1
Mikhail
  • 18,155
  • 5
  • 56
  • 129
  • Did you disable copy elision? Also, better post a real MCVE. – juanchopanza Apr 18 '15 at 12:15
  • 2
    @juanchopanza It actually is a real MCVE, just put the calling methods in the `main()`. The behaviour for `get_tuple` and `get_tuple_moved` is the same regardless of RVO, while `get_pair` is affected. – Mikhail Apr 18 '15 at 12:20
  • 1
    @Mikhail: so it is is actually _not_ a real MCVE (as it requires more work than copy&paste, although just a small bit of it) :-) – Dietmar Kühl Apr 18 '15 at 12:40
  • @DietmarKühl, ok, you are right, I'll fix it :) – Mikhail Apr 18 '15 at 12:41
  • 2
    http://stackoverflow.com/questions/25875596/can-returning-a-local-variable-by-value-in-c11-14-result-in-the-return-value-b/25876175#25876175 – T.C. Apr 18 '15 at 17:39
  • @T.C. Thanks! Didn't manage to find this question for myself. – Mikhail Apr 18 '15 at 17:49

1 Answers1

8

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.

0x499602D2
  • 87,005
  • 36
  • 149
  • 233