Можно ли преобразовать nullptr в uintptr_t? Разные компиляторы не согласны

10

Рассмотрим эту программу:

#include <cstdint>
using my_time_t = uintptr_t;

int main() {
    const my_time_t t = my_time_t(nullptr);
}

Не удалось скомпилировать с msvc v19.24:

<source>(5): error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'my_time_t'
<source>(5): note: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
<source>(5): error C2789: 't': an object of const-qualified type must be initialized
<source>(5): note: see declaration of 't'

Compiler returned: 2

но clang (9.0.1) и gcc (9.2.1) «едят» этот код без каких-либо ошибок.

Мне нравится поведение MSVC, но подтверждается ли оно стандартом? Другими словами, это ошибка в clang / gcc или возможно интерпретировать стандарт, что это правильное поведение от gcc / clang?

user1244932
источник
2
Я прочитал это как копию инициализации из приведения в стиле функции. Это затем интерпретируется компилятором как одно из приведений C ++ «даже если его нельзя скомпилировать». Возможно, между компиляторами существует несогласованность в том, как интерпретируется приведение
wreckgar23
Насколько я знаю, MSVC v19.24 не поддерживает режим C ++ 11. Вы имели в виду C ++ 14 или C ++ 17 вместо этого?
грецкий орех

Ответы:

5

По моему мнению, MSVC не ведет себя в соответствии со стандартами.

Я основываю этот ответ на C ++ 17 (проект N4659), но C ++ 14 и C ++ 11 имеют эквивалентную формулировку.

my_time_t(nullptr)является постфиксным выражением, а поскольку my_time_tявляется типом и (nullptr)представляет собой одиночное выражение в списке инициализаторов в скобках, оно в точности эквивалентно явному приведенному выражению. ( [expr.type.conv] / 2 )

Явное приведение пытается выполнить несколько различных конкретных приведений C ++ (с расширениями), в частности также reinterpret_cast. ( [expr.cast] /4.4 ) Проверенные ранее приведения reinterpret_castявляются const_castи static_cast(с расширениями, а также в комбинации), но ни одно из них не может привести std::nullptr_tк целочисленному типу.

Но это reinterpret_cast<my_time_t>(nullptr)должно произойти, потому что [expr.reinterpret.cast] / 4 говорит, что значение типа std::nullptr_tможет быть преобразовано в целочисленный тип, как если бы reinterpret_cast<my_time_t>((void*)0), что возможно, потому что my_time_t = std::uintptr_tдолжен быть тип, достаточно большой, чтобы представлять все значения указателя, и при этом условии тот же стандартный абзац допускает преобразование void*в интегральный тип.

Особенно странно, что MSVC разрешает преобразование, если используется нотация приведения, а не функциональная нотация:

const my_time_t t = (my_time_t)nullptr;
грецкий орех
источник
1
Ага. Обратите внимание, что, static_castв частности, есть некоторые случаи, предназначенные для захвата лестницы приведения в стиле C (например, приведение в стиле C к неоднозначному основанию скорее плохо сформировано static_cast, чем reinterpret_cast), но ни один из них здесь не применим.
ТЦ
my_time_t(nullptr)по определению то же (my_time_t)nullptr, что и MSVC, безусловно, неправильно принимать одно и отвергать другое.
Ричард Смит
2

Хотя я не могу найти явного упоминания в этом рабочем проекте C ++ Standard (от 2014 года), что преобразование std::nullptr_tв целочисленный тип запрещено, также нет упоминания о том, что такое преобразование разрешено!

Однако, в случае перехода от std::nullptr_tTo bool будет явно упомянуто:

4.12. Булевы преобразования
. Значение арифметики, перечисление с незаданной областью, указатель или указатель на тип элемента может быть преобразовано в значение типа bool. Нулевое значение, нулевое значение указателя или нулевое значение указателя члена преобразуется в ложь; любое другое значение преобразуется в true. Для прямой инициализации (8.5) значение типа std :: nullptr_t можно преобразовать в значение типа bool; результирующее значение ложно.

Кроме того, единственное место в этом проекте документа, где std::nullptr_tупоминается преобразование в целочисленный тип, находится в разделе «reinterpret_cast»:

5.2.10. Переосмысление приведения
...
(4) Указатель может быть явно преобразован в любой целочисленный тип, достаточно большой для его хранения. Функция отображения определяется реализацией. [Примечание: он предназначен для тех, кто знает структуру адресации базовой машины. - примечание конца] Значение типа std :: nullptr_t может быть преобразовано в целочисленный тип; преобразование имеет то же значение и действительность, что и преобразование (void *) 0 в целочисленный тип. [Примечание: reinterpret_cast нельзя использовать для преобразования значения любого типа в тип std :: nullptr_t. - конец примечания]

Итак, из этих двух наблюдений можно (ИМХО) обоснованно предположить, что MSVCкомпилятор верен.

РЕДАКТИРОВАТЬ : Тем не менее, использование «функциональной нотации» может фактически предложить обратное! У MSVCкомпилятора нет проблем с использованием приведения в стиле C, например:

uintptr_t answer = (uintptr_t)(nullptr);

но (как в вашем коде) он жалуется на это:

uintptr_t answer = uintptr_t(nullptr); // error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'uintptr_t'

Тем не менее, из того же проекта стандарта:

5.2.3 Явное преобразование типов (функциональная запись)
(1) Спецификатор простого типа (7.1.6.2) или спецификатор typename (14.6), за которым следует список выражений в скобках, создает значение указанного типа по заданному списку выражений. Если список выражений является одним выражением, выражение преобразования типа эквивалентно (в определенности и если определено в значении) соответствующему приведенному выражению (5.4). ...

«Соответствующее выражение приведения (5.4)» может относиться к приведению в стиле C.

Адриан Моул
источник
0

Все они соответствуют стандарту (см. Черновик n4659 для C ++).

nullptr определяется в [lex.nullptr] как:

Указатель-литерал - это ключевое слово nullptr. Это значение типа std :: nullptr_t. [Примечание: ... значение этого типа является константой нулевого указателя и может быть преобразовано в значение нулевого указателя или значение указателя нулевого элемента.]

Даже если примечания не являются нормативными, из этого ясно, что для стандарта nullptrожидается преобразование в нулевое значение указателя .

Позже мы найдем в [conv.ptr]:

Константа нулевого указателя является целочисленным литералом со значением ноль или значением типа std :: nullptr_t. Константа нулевого указателя может быть преобразована в тип указателя; .... Константа нулевого указателя целочисленного типа может быть преобразована в значение типа std :: nullptr_t.

Здесь опять то, что требуется стандартом, это то, что 0может быть преобразовано в a, std::nullptr_tи это nullptrможет быть преобразовано в любой тип указателя.

Насколько я понимаю, в стандарте нет требования о том, nullptrможет ли он быть непосредственно преобразован в целочисленный тип или нет. С этого момента:

  • MSVC имеет строгое чтение и запрещает преобразование
  • Clang и gcc ведут себя так, как если бы void *была вовлечена промежуточная конверсия.
Серж Баллеста
источник
1
Я думаю, что это неправильно. Некоторые константы нулевого указателя являются целочисленными литералами со значением ноль, но nullptrне потому, что они имеют нецелый тип std::nullptr_t. 0 можно преобразовать в std::nullptr_tзначение, но не в литерал nullptr. Это все намеренно, std::nullptr_tэто более ограниченный тип для предотвращения непреднамеренных преобразований.
MSalters
@MSalters: я думаю, что ты прав. Я хотел перефразировать это и сделал это неправильно. Я отредактировал свой пост с вашим комментарием. Спасибо за помощь.
Серж Баллеста