Search code examples
c++ternary-operatorternary

Ternary operator evaluation order


class Foo {
  public:
  explicit Foo(double item) : x(item) {}

  operator double() {return x*2.0;}

  private:
  double x;
}

double TernaryTest(Foo& item) {
  return some_condition ? item : 0;
}

Foo abc(3.05);
double test = TernaryTest(abc);

In the above example, why is test equal to 6 (instead of 6.1) if some_condition is true?

Changing the code like below returns value of 6.1

double TernaryTest(Foo& item) {
  return some_condition ? item : 0.0; // note the change from 0 to 0.0
}

It seems that (in the original example) the return value from Foo::operator double is cast to an int and then back to a double. Why?


Solution

  • The conditional operator checks conversions in both directions. In this case, since your constructor is explicit (so the ?: is not ambiguous), the conversion from Foo to int is used, using your conversion function that converts to double: That works, because after applying the conversion function, a standard conversion that converts the double to int (truncation) follows. The result of ?: in your case is int, and has the value 6.

    In the second case, since the operand has type double, no such trailing conversion to int takes place, and thus the result type of ?: has type double with the expected value.

    To understand the "unnecessary" conversions, you have to understand that expressions like your ?: are evaluated "context-free": When determining the value and type of it, the compiler doesn't consider that it's the operand of a return for a function returning a double.


    Edit: What happens if your constructor is implicit? The ?: expression will be ambiguous, because you can convert an int to an rvalue of type Foo (using the constructor), and a Foo to an rvalue of type int (using the conversion function). The Standard says

    Using this process, it is determined whether the second operand can be converted to match the third operand, and whether the third operand can be converted to match the second operand. If both can be converted, or one can be converted but the conversion is ambiguous, the program is ill-formed.


    Paragraphs explaining how your Foo is converted to int:

    5.16/3 about condition ? E1 : E2:

    Otherwise, if the second and third operand have different types, and either has (possibly cv-qualified) class type, an attempt is made to convert each of those operands to the type of the other. [...] E1 can be converted to match E2 if E1 can be implicitly converted to the type that expression E2 would have if E2 were converted to an rvalue (or the type it has, if E2 is an rvalue).

    4.3 about "implicitly converted":

    An expression e can be implicitly converted to a type T if and only if the declaration T t = e; is well-formed, for some invented temporary variable t.

    8.5/14 about copy initialization ( T t = e; )

    If the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.

    13.3.1.5 about the conversion function candidates

    The conversion functions of S and its base classes are considered. Those that are not hidden within S and yield type T or a type that can be converted to type T via a standard conversion sequence (13.3.3.1.1) are candidate functions.