Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Result of ternary operator not an rvalue

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.

like image 662
Pait Avatar asked Aug 26 '12 18:08

Pait


People also ask

Does the ternary operator return lvalue?

"the ternary operator returns always an rvalue. surprisingly it does" Do you meant "doesn't", as it can return lvalue (when both side are lvalues). In C, the conditional operator never yields an lvalue. In C++, it sometimes does.

What is the return type of conditional operators?

Conditional AND The operator is applied between two Boolean expressions. It is denoted by the two AND operators (&&). It returns true if and only if both expressions are true, else returns false.

Is ternary faster than if else C?

It is not faster. There is one difference when you can initialize a constant variable depending on some expression: const int x = (a<b) ?


1 Answers

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.

like image 105
aschepler Avatar answered Oct 29 '22 14:10

aschepler