decltype(void())和decltype(void {})之间的差异

时间:2016-09-02 21:01:27

标签: c++ c++11 language-lawyer void decltype

这是问题的后续跟踪: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;
}

2 个答案:

答案 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{}

实际发生了什么
  • 8.6 / 16 => 直接初始化
  • 8.6 / 17.1 => list-initialized ,跳转到8.6.4
  • 8.6.4 / 3.10 => 值初始化
    • void()将具有上述三个捷径,其中包含8.6 / 11 => value-initialized ,然后重新加入跟踪,这就是为什么需要 [expr.type.conv] 中的特殊例外。
  • 8.6 / 8.4 => 零初始化

现在,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-specifiertypename-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。

效果与我们的目的相同,changeP0135R1 Wording for guaranteed copy elision through simplified value categories相关,后者从表达式中删除了临时对象。相反,如果上下文需要,表达式的上下文提供了由表达式初始化的结果对象。

如上所述,decltype(与sizeoftypeid不同)并不为表达式提供结果对象,这就是为什么void()能够正常工作的原因虽然它无法初始化结果对象。

我觉得N4618 5.2.3 [expr.type.conv] 中的例外情况也应该适用于void{}。这意味着{}周围的指南变得更加复杂。例如,请参阅C ++核心指南中的ES.23: Prefer the {} initializer syntax,该指南目前推荐decltype(void{})超过decltype(void())decltype(T{})更有可能是这个人咬你的地方......