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



这是问题的后续:decltype(void())中的void()到底是什么意思?。


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;
}

void()在与sizeof一起使用时被解释为类型id
void()在与decltype一起使用时被解释为一个表达式。

我认为void{}在任何情况下都是无效的。它既不是有效的类型id,也不是有效的表达式。

(基于问题评论中的讨论)

注意:我引用的是C++17或接近的答案。C++14的工作方式相同,文本差异在答案末尾附近被注意到。

void()是一个特殊的例外。参见N4618 5.2.3[expr.type.conv],强调矿:

1一个简单类型指定器(7.1.7.2)或类型名称指定器(14.6)后面跟着一个带括号的可选表达式列表或一个支持的初始化列表(初始化器)构造给定初始化器的特定类型的值。如果该类型是推导类类型的占位符,则在本节剩余部分中,它将被重载解析所选函数的返回类型所取代,用于类模板推导(13.3.1.8)。

2如果初始值设定项是带括号的单个表达式,则类型转换表达式与相应的强制转换表达式(5.4)等效(在定义上,如果在含义上定义)。如果类型为(可能为cv qualified)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=>列表已初始化,跳至8.6.4
  • 8.6.4/3.10=>初始化的值
    • void()会用8.6/11=>初始化的值来快捷上述三个值,然后重新加入跟踪,这就是为什么需要[expr.type.conv]中的特殊异常
  • 8.6/8.4=>零初始化

现在,8.6/6定义零初始化用于:

  • 标量
  • 非并集类类型
  • 活接头类型
  • 数组类型
  • 参考类型

N4618 3.9[基本类型]/9定义标量

算术类型(3.9.1)、枚举类型、指针类型、指向成员的指针类型(3.9.2)、std::nullptr_t以及这些类型的cv限定版本(3.9.3)统称为标量类型

N4618 3.9.1[基本.基本]/8定义算术类型

积分和浮点类型统称为算术类型

所以void不是算术类型,所以它不是标量。所以它不能被零初始化;所以它不能是值初始化/em>初始化,所以表达式无效。

除了初始化之外,void()void{}将以相同的方式工作,产生类型为voidprvalue表达式。即使对于不完整类型不能有结果对象,并且void总是不完整:

N4618 3.9.1[基本.基本]/9(粗体矿):

类型cv void是一个不完整的类型,无法完成;这样的类型具有一组空值。

decltype特别允许不完整类型:

N4618 7.1.7.2[下降类型.简单]/5(粗体矿):

如果decltype说明符的操作数是prvalue,则不应用临时物化转换(4.4),也不为prvalue提供结果对象prvalue的类型可能不完整


在C++14中,N4296 5.2.3[expr.type.conv]的措辞不同。支撑形式几乎是对带括号版本的事后考虑:

简单类型规范(7.1.6.2)或类型名称规范14.6),后跟带括号的表达式列表构造给定表达式列表的特定类型的值。如果表达式列表是单个表达式,则类型转换表达式与相应的强制转换表达式(5.4)等效(定义上和定义上)。如果指定的类型是类类型,则类类型应完整。如果表达式列表指定了多个值,则类型应为具有适当声明构造函数的类(8.5,12.1),并且表达式T(x1, x2, ...)在效果上等效于声明T t(x1, x2, ...);对于一些发明的临时变量t,其结果是t的值作为prvalue。

表达式T(),其中T是非数组完整对象类型或(可能是cv限定的)void类型的simple-type-specifiertypename-specifier,创建指定类型的prvalue,其值是通过初始化(8.5)类型T的对象而产生的值;void()情况不进行初始化。[注意:如果T是一个cv合格的非类类型,则在确定生成的prvalue的类型时(第5条),将丢弃cv合格符

类似地,简单类型指定器类型名称指定器后面跟着支持初始化列表创建了一个特定类型直接列表的临时对象,该临时对象用特定的支撑初始化列表初始化(8.5.4),其值为该临时对象作为prvalue。

对于我们的目的,效果是相同的,更改与P0135R1有关,通过简化的值类别来保证副本省略,这从表达式中删除了临时对象创建。相反,如果上下文需要,表达式的上下文提供了一个要由表达式初始化的结果对象

如上所述,decltype(与sizeoftypeid不同)不为表达式提供结果对象,这就是为什么void()即使不能初始化结果对象也能工作的原因。


我觉得N4618 5.2.3中的异常[expr.type.conv]也应该应用于void{}。这意味着围绕{}的指导方针变得更加复杂。例如,请参阅ES.23:首选C++核心指南中的{}初始值设定项语法,该指南目前建议使用decltype(void{})而不是decltype(void())decltype(T{})更有可能是这个咬你的地方。。。

相关内容

最新更新