考虑以下代码:
uint32_t foo(uint64_t x ) {
auto y = uint32_t { x };
return y;
}
它被认为是一种缩小转换,编译器觉得有必要警告我(GCC 9)甚至声明一个错误(clang 9):GodBolt。
我的问题:
- 为什么
uint32_t { x }
不那么明确static_cast<uint32_t>(x)
? - 为什么使用 clang 比使用 GCC 更严重,值得一个错误?
为什么
uint32_t { x }
不那么明确static_cast<uint32_t>(x)
?
这不是不那么明确,只是不允许。进行直接或复制列表初始化时不允许缩小转换。当你这样做时,你正在使用缩小转换auto y = uint32_t { x };
进行直接列表初始化。y
(保证复制省略意味着这里不再有临时)
为什么使用 clang 比使用 GCC 更严重,值得一个错误?
这取决于实施者。显然,clang 想要更严格并发出硬错误,但两者都很好。该标准仅要求给出诊断消息,并且警告或错误涵盖了这一点。
添加到@NathanOliver 的答案 - 如果我们像这样构造 32 位整数,警告和错误就会消失:
uint32_t foo(uint64_t x ) {
auto y = uint32_t(x);
return y;
}
所以,(x)
这里{x}
不是语义等价的(即使同一个构造函数最终会被调用,如果它是一个类)。标准中的不缩小保证显然仅适用于列表初始化 IIANM。
因此,如果您想格外小心(或者如果您不想被打扰,请使用括号),这是使用花括号初始化的动机。
来自https://en.cppreference.com/w/cpp/language/list_initialization:
缩小转化范围
list-initialization 通过禁止以下内容来限制允许的隐式转换:
...
- 从整数或无范围枚举类型转换为不能表示原始所有值的整数类型,除非 source 是一个常量表达式,其值可以精确存储在目标类型中
这听起来像 clang 比 gcc 更符合这里(尽管要注意我不是语言律师)*:标准规定,如果您使用初始化器列表,则不会有任何缩小转换的危险。这是一种有意识的设计选择,可以弥补语言中内置的相当混杂的隐式转换 - 并且您在示例中拼写出来的明确方式是附带的烦恼。
编辑:* 并没有花很长时间 - 根据 NathanOliver 的回答,cppreference 中的“不允许”在标准中转换为“实施者依赖”。这就是我不检查来源的结果。