Учитывая, что int **p1 и const int**p2, p1 == p2 хорошо сформирован?
Дана следующая функция:
void g(int **p1, const int**p2)
{
if (p1 == p2) { }
}
clang
(вернуться к версии 3.0) выдает это предупреждение ( смотрите его вживую):
warning: comparison of distinct pointer types ('int **' and 'const int **')
uses non-standard composite pointer type 'const int *const *'
[-Wcompare-distinct-pointer-types]
if (p1 == p2) { }
~~ ^ ~~
С помощью -pedantic-errors
флаги превращает это в ошибку. ни gcc
(вернуться к 4.3.6) ни Visual Studio
(2013) выдают предупреждение, согласно стандарту, это сравнение:
p1 == p2
хорошо сформирован?
В более общем смысле, если два многоуровневых указателя отличаются в своих cv-квалификациях, отличных от первого уровня, правильно ли выполнено сравнение с помощью оператора равенства или реляционных операторов?
1 ответ
До C++14 этот случай был плохо сформирован, и более общий случай с некоторыми исключениями был также плохо сформирован. Это описано в отчете о дефектах 1512: сравнение указателей и преобразований квалификации, в котором говорится:
Согласно пункту 2 пункта 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]. Это кажется слишком строгим для сравнения указателей, и текущие реализации принимают пример.
В отчете о дефектах указывается, что, несмотря на то, что это было неверно, реализации приняли такое сравнение. Этот коммит clang указывает на то, что он рассматривается как расширение, и указывает на gcc
а также EDG
также рассматривает это как расширение, вероятно, это также относится и к Visual Studio.
Это было решено в стандарте N3624: Основная проблема 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
[Примечание: учитывая одинаковые типы T1 и T2, эта конструкция гарантирует, что оба могут быть преобразованы в T3. - примечание конца] Тип составного указателя двух операндов 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". - конец примера]