nullptr 可以转换为 uintptr_t 吗?不同的编译器不同意

use*_*932 10 c++ gcc clang visual-c++ c++11

考虑这个程序:

#include <cstdint>
using my_time_t = uintptr_t;

int main() {
    const my_time_t t = my_time_t(nullptr);
}
Run Code Online (Sandbox Code Playgroud)

使用 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
Run Code Online (Sandbox Code Playgroud)

但是 clang (9.0.1) 和 gcc (9.2.1) “吃”了这段代码,没有任何错误。

我喜欢 MSVC 的行为,但是否符合标准?换句话说,它是 clang/gcc 中的错误还是可以解释标准,这是来自 gcc/clang 的正确行为?

wal*_*nut 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_castand static_cast(带有扩展和组合),但这些都不能std::nullptr_t转换为整数类型。

但是reinterpret_cast<my_time_t>(nullptr)应该会成功,因为[expr.reinterpret.cast]/4说一个类型的值std::nullptr_t可以像 by 一样转换为整型reinterpret_cast<my_time_t>((void*)0),这是可能的,因为my_time_t = std::uintptr_t应该是一个足够大的类型来表示所有的指针值,并且在这种情况下相同的标准段允许转换为void*整型。

特别奇怪的是,如果使用强制转换符号而不是函数符号,MSVC 确实允许转换:

const my_time_t t = (my_time_t)nullptr;
Run Code Online (Sandbox Code Playgroud)


Ser*_*sta 0

所有内容均符合标准(参考 C++ 草案 n4659)。

nullptr在 [lex.nullptr] 中定义为:

指针字面量是关键字 nullptr。它是 std::nullptr_t 类型的纯右值。[注:...,该类型的纯右值是空指针常量,可以转换为空指针值或空成员指针值。]

即使注释是非规范的,这一注释也清楚地表明,对于标准,nullptr预计将转换为空指针值。

我们稍后在[conv.ptr]中找到:

空指针常量是一个值为 0 的整数文字或类型为 std::nullptr_t 的纯右值。空指针常量可以转换为指针类型;.... 整型的空指针常量可以转换为 std::nullptr_t 类型的纯右值。

这里,标准再次要求的是0可以转换为 astd::nullptr_t并且nullptr可以转换为任何指针类型。

我的理解是,标准没有要求是否nullptr可以直接转换为整数类型。从那时起:

  • MSVC有严格的读取并禁止转换
  • void *Clang 和 gcc 的行为就像涉及中间转换一样。