Ответ 1
До С++ 14 этот случай был плохо сформирован, и более общий случай с некоторыми исключениями также был плохо сформирован. Это описано в отчет о дефекте 1512: сравнение указателей и конверсии квалификации, в котором говорится:
В соответствии с пунктом 5.9 [expr.rel], описывающим указатель сравнения,
Преобразования указателя (4.10 [conv.ptr]) и преобразования квалификации (4.4 [conv.qual]) выполняются на операндах указателя (или на операнд указателя и константа нулевого указателя, или на два нулевых указателя константы, по крайней мере один из которых не является неотъемлемым), чтобы привести их к их составной тип указателя.
Это, как представляется, делает следующий пример плохо сформированным,
bool foo(int** x, const int** y) { return x < y; // valid ? }
потому что int ** не может быть преобразован в const int **, в соответствии с правила 4.4 [conv.qual], пункт 4. Это кажется слишком строгим для сравнение указателей и текущие реализации принимают пример.
В отчете о дефектах указывается, хотя это было плохо сформировано, реализации приняли такие сравнения. Этот clang commit указывает, что он рассматривался как расширение и указывает, что оба gcc
и EDG
также рассматривают это как расширение, по-видимому, это также случай для Visual Studio.
Это было разрешено в стандарте N3624: Core Issue 1512: Сравнение указателей и конверсий квалификации, в котором говорится:
В настоящем документе представлены необходимые изменения рабочего проекта для решения основных проблем 583 и 1512. В частности, он делает
[...]
и
void g(int **p1, const int**p2) { if (p1 == p2) { ... } }
хорошо сформированы.
Также обратите внимание, что в встреча была принята, было отмечено, что эта только что кодифицированная существующая практика.
Среди других изменений в стандарте этот абзац был добавлен в конец раздела 5
[expr], который включает новый термин cv-комбинированный тип:
Cv-комбинированный тип двух типов T1 и T2 является типом T3, подобным T1 чья cv-квалификационная сигнатура (4.4) равна:
- для каждого j > 0, cv3, j является объединением cv1, j и cv2, j;
- если полученный cv3, j отличается от cv1, j или cv2, j, то const добавляется к каждому cv3, k для 0 < k < к.
[Примечание: для аналогичных типов T1 и T2, эта конструкция гарантирует, что обе они могут быть преобразованы в T3. -end note] Тип составного указателя двух операндов p1 и p2 имеющие типы T1 и T2 соответственно, где по меньшей мере один является указателем или указатель на тип члена или std:: nullptr_t:
- если оба p1 и p2 являются константами нулевого указателя, std:: nullptr_t;
- если p1 или p2 - константа нулевого указателя, T2 или T1 соответственно;
- если T1 или T2 является "указателем на cv1 void", а другим типом является "указатель на cv2 T", "указатель на cv12 void", где cv12 является объединением cv1 и cv2;
- если T1 является "указателем на cv1 C1", а T2 является "указателем на cv2 C2", где C1 ссылается на C2 или C2, ссылается на C1 (8.5.3), cv-комбинированный тип T1 и T2 или cv-комбинированный тип T2 и T1, соответственно;
- если T1 является "указателем на член C1 типа cv1 U1", а T2 является "указателем на элемент C2 типа cv2 U2", где C1 является ссылкой, связанным с C2 или C2 является ссылкой, связанной с C1 (8.5.3), cv-комбинированным типом T2 и T1 или cv-комбинированный тип T1 и T2 соответственно;
- если T1 и T2 похожи на многоуровневый смешанный указатель и указатель на типы членов (4.4), cv-комбинированный тип T1 и T2;
- В противном случае программа, которая требует определения типа составного указателя, плохо сформирована.
[Пример:
typedef void *p; typedef const int *q; typedef int **pi; typedef const int **pci;
Тип составного указателя p и q является "указателем на const void"; Тип составного указателя pi и pci - это "указатель на const-указатель на const int". -end example]