5

Consider the following:

#include <iostream>

#define trace(name) std::cout << #name << " (" << this << "), i = " << i << std::endl

class C
{
    C(C const&);
    C& operator=(C const&);
public:
    int i;
    C() : i(42) { trace(CTOR); }
    C(C&& other) : i(other.i) { trace(MOVE); other.i = 0; }
    C& operator=(C&& other) { trace(ASGN); other.i = 0; return *this; }
    ~C() { trace(DTOR); }
};

C
func1(bool c)
{
    C local;
    if (c)
        return local;
    else
        return C();
}

int
main()
{
    C local(func1(true));
    return 0;
}

Both MSC and g++ allow the return local, and use the move constructor (as shown by the output) when doing so. While this makes a lot of sense to me, and I think it probably should be the case, I can't find the text in the standard that authorizes it. As far as I can see, the argument to the move constructor must be either a prvalue (which it clearly isn't) or an xvalue; it is in fact an lvalue, which would make the return just as illegal as C other = local; in the function body (which does fail to compile).

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • http://en.wikipedia.org/wiki/Return_value_optimization – IdeaHat Oct 06 '14 at 18:01
  • Explanation here: http://en.wikipedia.org/wiki/Return_value_optimization – Richard Hodges Oct 06 '14 at 18:01
  • 4
    This isn't performing RVO. **and use the move constructor (as shown by the output)** – chris Oct 06 '14 at 18:02
  • 2
    I am not sure if the conditions for RVO are met, or how that relates to `xvalues`, but there is something that says that if the conditions for copy elision apply, the thing can be treated as an rvalue. The quote is [in my answer to a different question](http://stackoverflow.com/questions/25885065/how-can-a-unique-ptr-be-returned-by-value-without-stdmove/25885212#25885212). – juanchopanza Oct 06 '14 at 18:03
  • This isn't RVO. In fact, I had to modify the code (with the `if`) in order to prevent RVO from being used, even without optimization, by g++. With or without RVO, the private copy constructor _should_ make the code illegal, unless the move is legal. – James Kanze Oct 07 '14 at 08:30
  • 1
    @juanchopanza The conditions for RVO are met (although I'm pretty sure that MSC won't do RVO if there is more than one return in a function---at least that was the case in the past). It just didn't occur to me that there could be a link between the conditions for RVO and the use of move. Mike Seymour has answered with the quote I was looking for, which is the same passage as that you quote in the referenced article. Which is really all I needed: the fact that the object can be treated as if it were an rvalue. – James Kanze Oct 07 '14 at 08:37

2 Answers2

6

When move semantics where added to C++ in C++11, decisions where made about where to automatically do move construction.

The general rule that was followed was that implicit move should occur when copy elision would be legal.

Copy elision is legal when you have an anonymous object that you are copying to another instance. The compiler can legally elide the copy, and treat the two objects as one, with a lifetime equal to the union of the two objects lifetime.

The other time that copy elision is legal is when you return a local variable from a function. This was known as NRVO (named return value optimization). This optimization in C++03 let you declare a return value, and it is constructed directly in the place where the return value goes. When you return retval;, no copy happens.

This is basically impossible to do without inlining when you return multiple different objects at different spots in your function.

However, when move semantics where added, this place was the other spot where implicit move can occur. A return local_var; in a function returning a variable the same type as local_var can be elided, and failing that, you can implicitly move from local_var into the return value.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
4

C++11 12.8/32: 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.

Returning a local automatic variable meets the criteria for elision; treating it as if it were an rvalue selects the move constructor.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • I agree, plus there is no possibility of omitting completely the copy/move (p31) since that's not a temporary. – Marco A. Oct 06 '14 at 18:17
  • @MarcoA.: What do you mean? Copying a local variable for return can be elided, according to the first criterion of p31. (It might not happen in this example, since two different values can be returned, and at most one of those copies can be elided.) – Mike Seymour Oct 06 '14 at 18:19
  • Sorry, I was referring to the third part. I forgot to write it. – Marco A. Oct 06 '14 at 18:22
  • @MarcoA.: OK. But it can be elided per the first part. – Mike Seymour Oct 06 '14 at 18:22
  • That's what I was looking for. I was pretty sure that it was there somewhere, but I couldn't find it in the places I was looking. Thanks. – James Kanze Oct 07 '14 at 08:32