decltype(void()) 和 decltype(void{}) 的区别

Differences between decltype(void()) and decltype(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。
当与 decltype 一起使用时,void() 被解释为表达式。

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

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

注意:我参考了 C++17 或接近的答案。 C++14 的工作方式相同,文本差异会在答案末尾注明。

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

1 A simple-type-specifier (7.1.7.2) or typename-specifier (14.6) followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer. If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction (13.3.1.8) for the remainder of this section.

2 If the initializer is a parenthesized single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). If the type is (possibly cv-qualified) void and the initializer is (), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized (8.6) with the initializer. For an expression of the form T(), T shall not be an array type.

所以 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 定义了 zero-initialize for:

  • 标量
  • 非联合class类型
  • 联合类型
  • 数组类型
  • 引用类型

N4618 3.9 [basic.types]/9 定义 标量:

Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types(3.9.2), std::nullptr_t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types.

N4618 3.9.1 [basic.fundamental]/8 定义算术类型:

Integral and floating types are collectively called arithmetic types.

所以void不是算术类型,所以不是标量,所以不可能是零初始化,所以不能值初始化,所以不能直接初始化,所以表达式无效。

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

N4618 3.9.1 [basic.fundamental]/9(大胆的矿):

A type cv void is an incomplete type that cannot be completed; such a type has an empty set of values.

decltype 特别允许不完整的类型:

N4618 7.1.7.2 [decl.type.simple]/5(粗矿):

If the operand of a decltype-specifier is a prvalue, the temporary materialization conversion is not applied (4.4) and no result object is provided for the prvalue. The type of the prvalue may be incomplete.


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

A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed by a parenthesized expression-list constructs a value of the specified type given the expression list. If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). If the type specified is a class type, the class type shall be complete. If the expression list specifies more than a single value,the type shall be a class with a suitably declared constructor(8.5,12.1), and the expression T(x1, x2, ...) is equivalent in effect to the declaration T t(x1, x2, ...); for some invented temporary variable t, with the result being the value of t as a prvalue.

The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type, whose value is that produced by value-initializing (8.5) an object of type T; no initialization is done for the void() case. [Note: if T is a non-class type that is cv-qualified, the cv-qualifiers are discarded when determining the type of the resulting prvalue (Clause 5). —end note]

Similarly, a simple-type-specifier or typename-specifier followed by a braced-init-list creates a temporary object of the specified type direct-list-initialized (8.5.4) with the specified braced-init-list, and its value is that temporary object as a prvalue.

效果与我们的目的相同,即 change relates to P0135R1 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{}) 更有可能是这个咬你的地方...