这是问题的后续跟踪:What does the void()
in decltype(void())
mean exactly?。
decltype(void())
编译得很好,在这种情况下void()
的含义在上述问题中解释了(实际上在答案中)。
另一方面,我注意到decltype(void{})
没有编译。
它们之间的区别是什么(至少在decltype
的背景下)?
为什么第二个表达式没有编译?
为了完整起见,它遵循一个最小的(不是)工作示例:
int main() {
// this doesn't compile
//decltype(void{}) *ptr = nullptr;
// this compiles fine
decltype(void()) *ptr = nullptr;
(void)ptr;
}
答案 0 :(得分:5)
void()
一起使用时, sizeof
被解释为type-id
与void()
一起使用时,decltype
被解释为表达式。
我不认为void{}
在任何情况下都有效。它既不是有效的type-id也不是有效的表达式。
答案 1 :(得分:4)
(以问题评论中的讨论为基础)
注意:我正在引用C ++ 17或close-to作为答案。 C ++ 14以相同的方式工作,在答案结尾附近注明文本差异。
void()
是一个特例。见N4618 5.2.3 [expr.type.conv] ,强调我的:
1 简单类型指定 (7.1.7.2)或 typename-speci fi er (14.6)后跟括号可选的表达式列表或 braced-init-list (初始化程序)在给定初始化程序的情况下构造指定类型的值。如果类型是推导类类型的占位符,则替换为本节其余部分的类模板推导(13.3.1.8)的重载决策所选函数的返回类型。
2如果初始值设定项是带括号的单个表达式,则类型转换表达式与相应的强制转换表达式(5.4)等效(在定义中,如果在意义上定义)。 如果类型是(可能是cv-quali fi ed)void而初始值设定项是(),则表达式是指定类型的prvalue,不执行初始化。否则,表达式是指定类型的prvalue其结果对象使用初始化程序直接初始化(8.6)。对于T()形式的表达式,T不应是数组类型。
所以void()
仅有效,因为它在 [expr.type.conv] / 2 中明确标识为无初始化。 void{}
没有遇到该异常,因此它会尝试成为直接初始化的对象。
tl; dr 这里通过C ++ 14注意:您不能直接初始化 void
个对象。
通过N4618进行兔子攻击8.6 [dcl.init] ,了解void{}
void()
将具有上述三个捷径,其中包含8.6 / 11 => value-initialized ,然后重新加入跟踪,这就是为什么需要 [expr.type.conv] 中的特殊例外。现在,8.6 / 6定义零初始化:
N4618 3.9 [basic.types] / 9 定义标量:
算术类型(3.9.1),枚举类型,指针类型,指向成员类型的指针(3.9.2), std :: nullptr_t 以及这些类型的cv限定版本(3.9。 3)统称为标量类型。
N4618 3.9.1 [basic.fundamental] / 8 定义算术类型:
积分和浮动类型统称为算术类型。
因此void
不是算术类型,因此它不是标量,因此它不能零初始化< / em>,因此它不能值初始化,因此它不能直接初始化,因此表达式无效。
除了初始化之外,void()
和void{}
将以相同的方式工作,生成类型为void
的 prvalue 表达式。即使您不能为不完整类型设置结果对象,并且void
总是不完整:
N4618 3.9.1 [basic.fundamental] / 9 (大胆的):
类型cv void是一个不完整的类型,无法完成;这样的类型有一组空值。
decltype
特别允许不完整的类型:
N4618 7.1.7.2 [decl.type.simple] / 5 (大胆的):
如果 decltype-speci fi er 的操作数是prvalue,则不应用临时实现转换(4.4),并且不为prvalue提供结果对象。 prvalue的类型可能不完整。
在C ++ 14中,N4296 5.2.3 [expr.type.conv] 的措辞不同。支撑形式几乎是括号后的版本:
简单类型指定程序(7.1.6.2)或 typename-speci fi er (14.6),后跟带括号的表达式列表构造给定表达式列表的指定类型的值。如果表达式列表是单个表达式,则类型转换表达式与相应的强制转换表达式(5.4)等效(在定义中,如果在含义中定义)。如果指定的类型是类类型,则类类型应完整。如果表达式列表指定的值不止一个,则类型应为具有适当声明的构造函数的类(8.5,12.1),并且表达式
T(x1, x2, ...)
与声明T t(x1, x2, ...)
的效果相同;对于一些发明的临时变量t,结果是t的值作为prvalue。表达式T(),其中T是非数组完整对象类型的
simple-type-specifier
或typename-specifier
或(可能是cv-quali fi ed)void
类型,创建一个prvalue特定类型的值,其值是由值初始化(8.5)T类型的对象产生的;没有为void()情况进行初始化。 [注意:如果T
是cv-quali的非类型类型,则在确定结果prvalue的类型时会丢弃 cv-quali firs (第5条)。 -end note ]同样,简单类型指定或 typename-specier er 后跟 braced-init-list 会创建一个临时对象具有指定 braced-init-list 的特定类型direct-list-initialized(8.5.4),其值为临时对象作为prvalue。
效果与我们的目的相同,change与P0135R1 Wording for guaranteed copy elision through simplified value categories相关,后者从表达式中删除了临时对象。相反,如果上下文需要,表达式的上下文提供了由表达式初始化的结果对象。
如上所述,decltype
(与sizeof
或typeid
不同)并不为表达式提供结果对象,这就是为什么void()
能够正常工作的原因虽然它无法初始化结果对象。
我觉得N4618 5.2.3 [expr.type.conv] 中的例外情况也应该适用于void{}
。这意味着{}
周围的指南变得更加复杂。例如,请参阅C ++核心指南中的ES.23: Prefer the {} initializer syntax,该指南目前推荐decltype(void{})
超过decltype(void())
。 decltype(T{})
更有可能是这个人咬你的地方......