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{}
将以相同的方式工作,生成类型为 void
的 prvalue 表达式。即使您不能为不完整的类型提供 结果对象 ,并且 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
(与 sizeof
或 typeid
不同)不为表达式提供结果对象,这就是 void()
甚至可以工作的原因尽管它不能初始化结果对象。
我觉得 N4618 5.2.3 [expr.type.conv] 中的例外也应该适用于 void{}
。这意味着围绕 {}
的指南变得更加复杂。例如,参见 C++ 核心指南中的 ES.23: Prefer the {} initializer syntax,目前推荐 decltype(void{})
而不是 decltype(void())
。 decltype(T{})
更有可能是这个咬你的地方...
这是问题的后续:
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{}
将以相同的方式工作,生成类型为 void
的 prvalue 表达式。即使您不能为不完整的类型提供 结果对象 ,并且 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 declarationT 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
ortypename-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: ifT
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
(与 sizeof
或 typeid
不同)不为表达式提供结果对象,这就是 void()
甚至可以工作的原因尽管它不能初始化结果对象。
我觉得 N4618 5.2.3 [expr.type.conv] 中的例外也应该适用于 void{}
。这意味着围绕 {}
的指南变得更加复杂。例如,参见 C++ 核心指南中的 ES.23: Prefer the {} initializer syntax,目前推荐 decltype(void{})
而不是 decltype(void())
。 decltype(T{})
更有可能是这个咬你的地方...