С++ - Неявные преобразования с тернарным оператором

У меня есть следующий код:

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;
}

Попытка скомпилировать этот код с Visual Studio 2017 RC приводит к ошибке:
ternarytest.cpp(14): error C2446: ':': no conversion from 'B' to 'A'
что удивительно, потому что вы ожидаете, что он преобразует их как в общий тип (int).
Clang 4.0 скомпилирует тот же код успешно без каких-либо ошибок или предупреждений.

Какое из двух правильных в этом случае?

Ответ 1

clang прав (см. ссылки ниже). Поскольку нет возможных преобразований между A и B, разрешение перегрузки используется для определения конверсий, которые будут применяться к операндам, а следующая (фиктивная) перегрузка выбран:

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

Существует такая (опять-таки фиктивная) перегрузка оператора ?: для любой пары арифметических типов (см. ссылки ниже).


Стандартные правила:

Поскольку вы не можете преобразовать A в B или B в A, тогда применяется следующее ([expr.cond]):

В противном случае результатом будет prvalue. Если второй и третий операнды не имеют одного и того же типа, и имеет (возможно, cv-qualified) тип класса, разрешение перегрузки используется для определения конверсий (если есть) применяется к операндам (13.3.1.2, 13.6). Если разрешение перегрузки выходит из строя, программа плохо сформирована. В противном случае, указанные таким образом конверсии применяются, а преобразованные операнды используются вместо оригинала операндов для остальной части этого раздела.

Это относится к этому ([over.match.oper]):

Если любой из операндов имеет тип, который является классом или перечислением, пользовательская операторская функция может быть объявила, что реализует этот оператор, или может потребоваться пользовательское преобразование для преобразования операнда к типу, который подходит для встроенного оператора.

[...]

Набор функций-кандидатов для разрешения перегрузки - это объединение кандидатов-кандидатов, кандидатов, не являющихся членами, и встроенных кандидатов.

Если встроенный кандидат выбирается с помощью разрешения перегрузки, операнды типа класса преобразуются в типы соответствующих параметров выбранной функции работы, за исключением того, что второй стандарт последовательность преобразования пользовательской последовательности преобразований (13.3.3.1.2) не применяется.

В вашем случае есть встроенный кандидат (спасибо @cpplearner, [over.built]):

Для каждой пары продвинутых арифметических типов L и R существуют кандидатные операторные функции вида

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

где LR является результатом обычных арифметических преобразований между типами L и R. [Примечание. Как и во всех этих описаниях функций-кандидатов, это выражение служит только для описания встроенного оператора для целей разрешения перегрузки. Оператор "?:" Не может быть перегружен. - конечная нота]


Дополнительные сведения:

Так как оператор ?: не может быть перегружен, это означает, что ваш код работает только в том случае, если оба типа могут быть преобразованы в арифметический тип (int). В качестве "счетчика" -пример, следующий код плохо сформирован:

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

Также обратите внимание, что вы получите двусмысленный "вызов", если оба типа конвертируются в оба, например, int и float. Там ошибка (из gcc) на самом деле заполнена информацией:

ошибка: нет соответствия для тернарного оператора?:? (типы операндов: "bool", "A" и "B" )

auto c = true ? A{} : B{};
~~~~~^~~~~~~~~~~
  • note: кандидат: оператор?:( bool, int, int)
  • note: кандидат: оператор?:( bool, int, float)
  • note: кандидат: оператор?:( bool, float, int)
  • note: кандидат: оператор?:( bool, float, float)