Рассмотрим эту программу:
#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?
Ответы:
По моему мнению, 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 разрешает преобразование, если используется нотация приведения, а не функциональная нотация:
источник
static_cast
в частности, есть некоторые случаи, предназначенные для захвата лестницы приведения в стиле C (например, приведение в стиле C к неоднозначному основанию скорее плохо сформированоstatic_cast
, чемreinterpret_cast
), но ни один из них здесь не применим.my_time_t(nullptr)
по определению то же(my_time_t)nullptr
, что и MSVC, безусловно, неправильно принимать одно и отвергать другое.Хотя я не могу найти явного упоминания в этом рабочем проекте C ++ Standard (от 2014 года), что преобразование
std::nullptr_t
в целочисленный тип запрещено, также нет упоминания о том, что такое преобразование разрешено!Однако, в случае перехода от
std::nullptr_t
Tobool
будет явно упомянуто:Кроме того, единственное место в этом проекте документа, где
std::nullptr_t
упоминается преобразование в целочисленный тип, находится в разделе «reinterpret_cast»:Итак, из этих двух наблюдений можно (ИМХО) обоснованно предположить, что
MSVC
компилятор верен.РЕДАКТИРОВАТЬ : Тем не менее, использование «функциональной нотации» может фактически предложить обратное! У
MSVC
компилятора нет проблем с использованием приведения в стиле C, например:но (как в вашем коде) он жалуется на это:
Тем не менее, из того же проекта стандарта:
«Соответствующее выражение приведения (5.4)» может относиться к приведению в стиле C.
источник
Все они соответствуют стандарту (см. Черновик n4659 для C ++).
nullptr
определяется в [lex.nullptr] как:Даже если примечания не являются нормативными, из этого ясно, что для стандарта
nullptr
ожидается преобразование в нулевое значение указателя .Позже мы найдем в [conv.ptr]:
Здесь опять то, что требуется стандартом, это то, что
0
может быть преобразовано в a,std::nullptr_t
и этоnullptr
может быть преобразовано в любой тип указателя.Насколько я понимаю, в стандарте нет требования о том,
nullptr
может ли он быть непосредственно преобразован в целочисленный тип или нет. С этого момента:void *
была вовлечена промежуточная конверсия.источник
nullptr
не потому, что они имеют нецелый типstd::nullptr_t
. 0 можно преобразовать вstd::nullptr_t
значение, но не в литералnullptr
. Это все намеренно,std::nullptr_t
это более ограниченный тип для предотвращения непреднамеренных преобразований.