Question

If you compile this program with a C++11 compiler, the vector is not moved out of the function.

#include <vector>
using namespace std;
vector<int> create(bool cond) {
    vector<int> a(1);
    vector<int> b(2);

    return cond ? a : b;
}
int main() {
    vector<int> v = create(true);
    return 0;
}

If you return the instance like this, it is moved.

if(cond) return a;
else return b;

Here is a demo on ideone.

I tried it with gcc 4.7.0 and MSVC10. Both behave the same way.
My guess why this happens is this:
The ternary operators type is an lvalue because it is evaluated before return statement is executed. At this point a and b are not yet xvalues (soon to expire).
Is this explanation correct?

Is this a defect in the standard?
This is clearly not the intended behaviour and a very common case in my opinion.

Was it helpful?

Solution

Here are the relevant Standard quotes:

12.8 paragraph 32:

Copy elision is permitted in the following circumstances [...]

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
  • [when throwing, with conditions]
  • [when the source is a temporary, with conditions]
  • [when catching by value, with conditions]

paragraph 33:

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. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. - end note]

Since the expression in return (cond ? a : b); is not a simple variable name, it's not eligible for copy elision or rvalue treatment. Maybe a bit unfortunate, but it's easy to imagine stretching the example a little bit further at a time until you create a headache of an expectation for compiler implementations.

You can of course get around all this by explicitly saying to std::move the return value when you know it's safe.

OTHER TIPS

This will fix it

return cond ? std::move(a) : std::move(b);

Consider the ternary operator as a function, like your code is

return ternary(cond, a, b);

The parameters won't be moved implicitly, you need to make it explicit.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top