Проблема тонкая. Спецификация для определения составного типа операндов указателя условного выражения в C ++ похожа на те, что в C, поэтому она выглядит многообещающе:
(N4659) [expr.cond]
7 L-значение-в-значение, массив-указатель и функция-указатель
Стандартные преобразования выполняются на втором и третьем операндах.
После этих преобразований должно быть выполнено одно из следующих действий:
[...]
Один или оба из второго и третьего операндов имеют тип указателя; преобразование указателя, преобразование указателя функции и квалификация
преобразования выполняются, чтобы привести их к составному указателю
тип (пункт [expr]). Результат имеет тип составного указателя.
[...]
Редукция к типу составного указателя указывается следующим образом:
(N4659) [expr]
5 Составной указатель типа двух операндов p1 и p2, имеющих
типы T1 и T2 соответственно, где хотя бы один является указателем или
указатель на тип члена или std::nullptr_t
, это:
- если p1 и p2 являются константами нулевого указателя,
std::nullptr_t
;
- если p1 или p2 - константа нулевого указателя, T2 или T1 соответственно;
- если T1 или T2 - «указатель на cv1 void», а другой тип - «указатель на cv2 T», где T - тип объекта или void, «указатель на cv12 void»,
где cv12 - объединение cv1 и cv2;
- [...]
Таким образом, результат нашего макроса ICE_P
определяется тем, с какой из пуль выше мы высаживаем одну после проверки каждой по порядку. Учитывая то, как мы определили is_ice_helper
, мы знаем, что составной тип не является nullptr_t
, в противном случае мы нажмем первый маркер и получим ошибку из-за отсутствующей специализации шаблона. Таким образом, мы должны попасть в пулю № 3, делая предикатный отчет ложным. Кажется, все зависит от определения константы нулевого указателя.
(N4659) [conv.ptr] (выделено мной)
1 A константа нулевого указателя - это целочисленный литерал со значением
ноль или значение типа std::nullptr_t
. Нулевой указатель
константа может быть преобразована в тип указателя; результат нулевой
значение указателя этого типа и отличается от любого другого
значение указателя объекта или типа указателя на функцию. Такое преобразование
называется преобразованием нулевого указателя. Два значения нулевого указателя одного и того же
Тип должен сравниваться равным. Преобразование константы нулевого указателя в
указатель на cv-квалифицированный тип - это одиночное преобразование, а не
последовательность преобразования указателя с последующей квалификацией
преобразование. Константа нулевого указателя целочисленного типа может быть преобразована
до значения типа std::nullptr_t
.
Так как (int*)0
не является константой нулевого указателя по вышеприведенному определению, мы не можем претендовать на первый пункт [expr] / 5. Составной тип не std::nullptr_t
. (void *) ((x)*0)
не является константой нулевого указателя и не может быть преобразована в единицу. Удаление актерского состава (что не разрешено определением) оставляет нам (x)*0
. Это целочисленное константное выражение со значением ноль. Но это не целочисленный литерал со значением ноль! Определение константы нулевого указателя в C ++ отличается от определения в C!
(N1570) 6.3.2.3 Указатели
3 Целочисленное константное выражение со значением 0 или такое
выражение, приведенное к типу void *
, называется константой нулевого указателя. Если
константа нулевого указателя преобразуется в тип указателя, в результате
указатель, называемый нулевым указателем, гарантированно сравнивает неравное с
указатель на любой объект или функцию.
C аллоws произвольные константные выражения со значением ноль для формирования константы нулевого указателя, в то время как C ++ требует целое число литералы .Учитывая богатую поддержку C ++ для вычисления константных выражений различных литеральных типов, это кажется ненужным ограничением.И тот, который делает вышеуказанный подход к ICE_P
не стартером в C ++.