Проблема тонкая. Спецификация для определения составного типа операндов-указателей условного выражения в C++ аналогична спецификациям в C, поэтому сначала она выглядит многообещающе:
(N4659) [expr.cond]
7 Lvalue-to-rvalue, массив-указатель , а стандартные преобразования функции в указатель выполняются для второго и третьего операндов. После этих преобразований должно выполняться одно из следующих условий:
[...]
Один или оба из второго и третьего операндов имеют тип указателя; преобразования указателя, преобразования указателя функции и преобразования квалификации выполняются, чтобы привести их к составному типу указателя (пункт [expr]). Результат имеет тип составного указателя.
[...]
Приведение к составному типу указателя определяется следующим образом:
(N4659) [выражение]
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 константа нулевого указателя целочисленный литерал с нулевым значением или значением prvalue типа std::nullptr_t
. Константа нулевого указателя может быть преобразована в тип указателя; результатом является нулевое значение указателя этого типа, и его можно отличить от любого другого значения типа указателя объекта или указателя функции. Такое преобразование называется преобразованием нулевого указателя. Два нулевых значения указателя одного и того же типа должны сравниваться как равные. Преобразование константы нулевого указателя в указатель на тип cv-qualified является одиночным преобразованием, а не последовательностью преобразования указателя, за которым следует преобразование уточнения. Константа нулевого указателя целочисленного типа может быть преобразована в prvalue типа 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 позволяет произвольным константным выражениям с нулевым значением формировать константу нулевого указателя, в то время как C++ требует целочисленных литералов. Учитывая богатую поддержку C++ для вычисления константных выражений различных литеральных типов, это кажется ненужным ограничением. И тот, который делает описанный выше подход к ICE_P
неприемлемым для C++.
person
StoryTeller - Unslander Monica
schedule
21.04.2019