【问题标题】:Why is an explicit construction considered an (implicit) narrowing conversion?为什么将显式构造视为(隐式)缩小转换?
【发布时间】:2020-01-21 02:39:33
【问题描述】:
考虑以下代码:
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 更严重,导致错误?
【问题讨论】:
标签:
c++
g++
language-lawyer
clang++
narrowing
【解决方案1】:
为什么uint32_t { x } 不如static_cast<uint32_t>(x) 明确?
这不是不太明确,只是不允许。进行直接或复制列表初始化时不允许缩小转换。当您执行auto y = uint32_t { x }; 时,您正在通过缩小转换直接列表初始化y。 (保证复制省略意味着这里不再有临时)
为什么使用 clang 比使用 GCC 更严重,值得出错?
这取决于实施者。显然,clang 想要更严格并发出硬错误,但两者都很好。该标准只要求给出诊断消息,并给出警告或错误。
【解决方案2】:
添加到@NathanOliver 的答案 - 如果我们像这样构造 32 位整数,警告和错误就会消失:
uint32_t foo(uint64_t x ) {
auto y = uint32_t(x);
return y;
}
所以,这里的(x) 和{x} 在语义上不等效(即使同一个构造函数最终会被调用,如果它是一个类的话)。标准中的不缩小保证显然仅适用于列表初始化,IIANM。
因此,如果您想格外小心(或者如果您不想被打扰,请使用括号。)
【解决方案3】:
来自https://en.cppreference.com/w/cpp/language/list_initialization:
缩小转化范围
list-initialization 通过以下方式限制允许的隐式转换
禁止:
...
- 从整数或无范围枚举类型转换为不能表示原始所有值的整数类型,除非 source
是一个常量表达式,其值可以精确地存储在
目标类型
这听起来像是 clang 比 gcc 更符合这里(尽管请注意我不是语言律师)*:标准规定,如果您使用初始化列表,则不会有任何缩小转换的危险。这是一种有意识的设计选择,可以弥补语言中内置的相当混杂的隐式转换 - 而且您在示例中清楚地拼写它的方式无疑是一个附带的烦恼。
编辑:* 并没有花很长时间 - 根据 NathanOliver 的回答,cppreference 中的“不允许”似乎转换为标准中的“实施者依赖”。这就是我不检查来源的结果。