neverlastn neverlastn - 22 days ago 8
C++ Question

Is it safe to create a const reference to result of ternary operator in C++?

There's something quite non-obvious going on in this code:

float a = 1.;

const float & x = true ? a : 2.; // Note: `2.` is a double

a = 4.;

std::cout << a << ", " << x;


both clang and gcc output:

4, 1


One would naively expect the same value printed twice but this isn't the case. The issue here has nothing to do with the reference. There are some interesting rules dictating the type of
? :
. If the two arguments are of different type and can be casted, they will by using a temporary. The reference will point to the temporary of
? :
.

The example above compiles fine and it might or might not issue a warning while compiling with
-Wall
depending on the version of your compiler.

Here's an example on how easy it's to get this wrong in legitimate-looking code:

template<class Iterator, class T>
const T & min(const Iterator & iter, const T & b)
{
return *iter < b ? *iter : b;
}

int main()
{
// Try to remove the const or convert to vector of floats
const std::vector<double> a(1, 3.0);

const double & result = min(a.begin(), 4.);

cout << &a[0] << ", " << &result;
}


If your logic after this code assumes that any changes on
a[0]
will be reflected to
result
, it will be wrong in cases where
?:
creates a temporary. Also, if at some point you make a pointer to
result
and you use it after
result
goes out of scope, there will be a segmentation fault despite the fact that your original
a
hasn't gone out of scope.

I feel there're serious reasons NOT to use this form beyond "maintainability and reading issues" mentioned here especially while writing templated code where some of your types and their const'ness might be out of your control.

So my question is, is it safe to use
const &
s on ternary operators?

P.S. Bonus example 1, extra complications (see also here):

float a = 0;
const float b = 0;
const float & x = true ? a : b;

a = 4;
cout << a << ", " << x;


clang output:

4, 4


gcc 4.9.3 output:

4, 0


With clang this example compiles and runs as expected but with up to recent versions of gcc (

P.S.2 Bonus example 2, great for interviews ;) :

double a = 3;

const double & a_ref = a;

const double & x = true ? a_ref : 2.;

a = 4.;

std::cout << a << ", " << x;


output:

4, 3

M.M M.M
Answer

First of all, the result of the conditional operator is either a glvalue designating the selected operand, or a prvalue whose value comes from the selected operand.

Exception as noted by T.C.: if at least one operand is of class type and has a conversion-to-reference operator, the result may be an lvalue designating the object designated by the return value of that operator; and if the designated object is actually a temporary, a dangling reference may result. This is a problem with such operators that offer implicit conversion of prvalues to lvalues, not a problem introduced by the conditional operator per se.

In both cases it is safe to bind a reference to the result, the usual rules for binding a reference to an lvalue or a prvalue apply. If the reference binds to a prvalue (either the prvalue result of the conditional, or a prvalue initialized from the lvalue result of the conditional), the lifetime of the prvalue is extended to match the lifetime of the reference.


In your original case, the conditional is:

true ? a : 2.

The second and third operand are: "lvalue of type float" and "prvalue of type double". This is case 5 in the cppreference summary, with the result being "prvalue of type double".

Then, your code initializes a const reference with a prvalue of a different (non-reference-related) type. The behaviour of this is to copy-initialize a temporary of the same type as the reference.

In summary, after const float & x = true ? a : 2.;, x is an lvalue denoting a float whose value is the result of converting a to double and back. (Not sure off the top of my head whether that is guaranteed to compare equal to a). x is not bound to a.


In bonus case 1, the second and third operand of the conditional operator are "lvalue of type float" and "lvalue of type const float". This is case 3 of the same cppreference link,

both are glvalues of the same value category and have the same type except for cv-qualification

The behavour is that the second operand is converted to "lvalue of type const float" (denoting the same object), and the result of the conditional is "lvalue of type const float" denoting the selected object.

Then you bind const float & to "lvalue of type const float", which binds directly.

So after const float & x = true ? a : b;, x is directly bound to either a or b.


In bonus case 2, true ? a_ref : 2. . The second and third operands are "lvalue of type const double" and "prvalue of type double", so the result is "prvalue of type double".

Then you bind this to const double & x, which is a direct binding since const double is reference-related to double.

So after const double & x = true ? a_ref : 2.; , then x is an lvalue denoting a double with the same value as a_ref (but x is not bound to a).

Comments