Integ_constants в тройном операторе

MSVC и clang/gcc не согласны с тем, можно ли использовать два разных интегральных константы в тройном операторе (и, как следствие, имеют ли они common_type):

#include <utility>

int main()
{
    return false
        ? std::integral_constant<int, 1>() 
        : std::integral_constant<int, 2>();
}

Вышеупомянутый фрагмент компилируется в clang и gcc, но не в MSVC. Какое правильное поведение соответствует стандарту? И если это поведение clang/gcc, то каковы последовательности преобразования, используемые для вывода общего типа этих двух разных типов?

Ответ 1

TL;DR; Код хорошо сформирован. Условное выражение будет иметь тип int и значение 2. Это ошибка MSVC.


Из [expr.cond]:

В противном случае, если второй и третий операнды имеют разные типы и имеют (возможно, cv-qualified) тип класса или [...], делается попытка сформировать неявную последовательность конверсий (13.3.3.1) из каждого из этих операндов к типу другого. [...] Делаются попытки создать неявную последовательность преобразований из выражения операнда E1 типа T1 в целевой тип, относящийся к типу T2 выражения операнда E2 следующим образом: [...]
- Если E2 является lvalue, [...]
- Если E2 является значением x, [...]
- Если E2 является prvalue или если ни одна из преобразованных последовательностей выше не может быть сформирована и по меньшей мере одна из операнды имеют (возможно, cv-qualified) тип класса:
      - если T1 и T2 являются одним и тем же типом класса (игнорируя cv-квалификацию) и T2, по меньшей мере, cv-квалифицируется как T1, целевой тип T2,
            - в противном случае целевой тип - это тип, который E2 имел бы после применения lvalue-to-rvalue (4.1), (4.2) и стандартное преобразование по функциям (4.1).

Итак, мы пытаемся сформировать неявную последовательность преобразований из типа std::integral_constant<int, 1>, чтобы напечатать std::integral_constant<int, 2>. Это нежизнеспособно. Не существует также и неявной последовательности преобразования в обратном направлении. Эти типы просто не взаимозаменяемы.

Итак, мы продолжаем:

Если преобразование отсутствует последовательность может быть сформирована, операнды остаются неизмененными и дальнейшая проверка выполняется, как описано ниже. [...]

Если второй и третий операнды являются glvalues ​​одной и той же категории значений и имеют один и тот же тип, [...]

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

Хорошо, какое разрешение перегрузки мы можем выполнить? Из [over.match.oper]:

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

Если встроенные функции указаны в [over.built] как:

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

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

где LR является результатом обычных арифметических преобразований между типами L и R.

Один из этих встроенных элементов будет int operator?:(bool, int, int). Так как std::integral_constant<int, V> имеет operator int(), это является жизнеспособным преобразованием для обоих аргументов.

Мы продолжаем в [expr.cond]:

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

Выполняются стандартные значения преобразования Lvalue-to-rvalue (4.1), преобразования по методу "массив-в-указатель" (4.2) и стандартного преобразования функции-to-pointer (4.3) на втором и третьем операндах. После этих преобразований будет выполнено одно из следующих условий:
- Второй и третий операнды имеют один и тот же тип; результат этого типа и объект результата инициализируется с использованием выбранного операнда.

В этот момент второй и третий операнды имеют один и тот же тип: int. Таким образом, объект результата инициализируется как int, и выражение хорошо сформировано.

Ответ 2

Соответствующий абзац из [expr.cond] 6:

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

integral_constant<int> имеет оператор преобразования в int, поэтому это может работать. Следуя 13.3.1.2, мы видим, что пункт 3.2, все встроенные операторы ?:, принимающие целые и аргументы с плавающей запятой, являются кандидатами.

Теперь для всех этих задач выполняется разрешение перегрузки, учитывая наши три аргумента. Согласно [over.ics.rank]/3.3, мы связываем разрывы, сравнивая стандартные последовательности преобразования из int (возвращаемый тип integral_constant<int>) к параметрам типов встроенных операторов.

Однако достаточно взглянуть на таблицу 13; преобразования в типы с плавающей точкой имеют ранг конверсии, а поскольку int является продвинутым типом, преобразование в любой интегральный тип , но int (который является преобразованием идентичности) является интегральным преобразованием с рангом конверсии, Следовательно, лучший жизнеспособный кандидат, недвусмысленно, operator?:(bool, int, int). То есть, MSVC ошибочен.