Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange implicit conversions with the ternary operator

I have the following code:

class A {
public:
    operator int() const { return 5; }
};

class B {
public:
    operator int() const { return 6; }
};

int main() {
    A a;
    B b;
    int myInt = true ? a : b;
    return 0;
}

Attempting to compile that code with Visual Studio 2017 RC results in the following error:

error C2446: :: no conversion from B to A

note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

...which is surprising because I would expect it to convert both of them to a common type, in this case int.

clang (4.0) compiles the same code successfully without any errors or warnings.

Which of the two is correct in this case, and why?

like image 698
user1000039 Avatar asked Jan 13 '17 14:01

user1000039


1 Answers

TL;DR; clang is right, since there are no possible conversions between A and B, overload resolution is used to determine the conversions to be applied to the operands, and the following (fictive) overloaded operator is selected:

int operator?:(bool, int, int);

There exists such (again, fictive) overload of the ?: operator for any pair of arithmetic types (see references below).


Standard rules:

Since you cannot convert A to B or B to A, then the following applies:

[expr.cond]

Otherwise, the result is a prvalue. If the second and third operands do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands ([over.match.oper], [over.built]). If the overload resolution fails, the program is ill-formed. Otherwise, the conversions thus determined are applied, and the converted operands are used in place of the original operands for the remainder of this subclause.

This falls back to this:

[over.match.oper]

If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator.

[...]

The set of candidate functions for overload resolution is the union of the member candidates, the non-member candidates, and the built-in candidates.

If a built-in candidate is selected by overload resolution, the operands of class type are converted to the types of the corresponding parameters of the selected operation function, except that the second standard conversion sequence of a user-defined conversion sequence is not applied. Then the operator is treated as the corresponding built-in operator and interpreted according to [expr.compound].

In your case, there is a built-in candidate:

[over.built#27]

For every pair of promoted arithmetic types L and R, there exist candidate operator functions of the form

LR      operator?:(bool, L, R);

where LR is the result of the usual arithmetic conversions ([expr.arith.conv]) between types L and R. [ Note: As with all these descriptions of candidate functions, this declaration serves only to describe the built-in operator for purposes of overload resolution. The operator “?:” cannot be overloaded. — end note ]


Extra details:

Since the ?: operator cannot be overloaded, this means that your code only works if both types can be converted to an arithmetic type (e.g., int). As a "counter"-example, the following code is ill-formed:

struct C { };
struct A { operator C() const; };
struct B { operator C() const; };

auto c = true ? A{} : B{}; // error: operands to ?: have different types 'A' and 'B'

Also note that you would get an ambiguous "call" if one of the type is convertible to two different arithmetic types, e.g., int and float:

struct A { operator int() const; };
struct B { 
    operator int() const; 
    operator float() const;
};

auto c = true ? A{} : B{};

The error (from gcc) is actually full of information:

error: no match for ternary 'operator?:' (operand types are 'bool', 'A', and 'B')

auto c = true ? A{} : B{};
~~~~~^~~~~~~~~~~
  • note: candidate: operator?:(bool, float, int) <built-in>
  • note: candidate: operator?:(bool, float, float) <built-in>
like image 87
Holt Avatar answered Nov 10 '22 11:11

Holt