【问题标题】:Differences between decltype(void()) and decltype(void{})decltype(void()) 和 decltype(void{}) 的区别
【发布时间】:2016-09-02 21:01:27
【问题描述】:

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

【问题讨论】:

  • 因为“List-initialization 是从一个花括号初始化列表中初始化一个对象或引用。” :) 但是等等.. 那不是真的! int{} 不初始化对象,我听到你说。好吧,然后是有很多案例的大项目符号列表,但没有 void 案例:)
  • @JohannesSchaub-litb 好吧,touché,但它可以与int 一起使用。它既不是对象也不是引用。为什么不能在未评估的上下文中与void 一起使用?
  • @W.F.禁止有关未使用变量的警告,仅此而已。 :-)
  • @JohannesSchaub-litb 啊哈哈你在我写我的时候修改了你的评论!!那个子弹清单在哪里?该死!
  • @skypjack 从概念上讲,type{} 不是演员表的退化情况,就像type() 一样。它只共享其大部分语法。由于前者是“统一初始化”,所以当你写type t{}: member{} 等时,它的行为是一样的。在所有情况下,void 都是不允许的。所以,这里是统一的病态。

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


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    (基于问题 cmets 中的讨论)

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

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

    1 simple-type-specifier (7.1.7.2) 或 typename-specifier (14.6) 后跟括号可选的 expression-list 或由 braced-init-list(初始化程序)构造一个给定初始化程序的指定类型的值。如果该类型是推导类类型的占位符,则在本节的其余部分中,将其替换为由重载决议选择的用于类模板推导 (13.3.1.8) 的函数的返回类型。

    2 如果初始值设定项是带括号的单个表达式,则类型转换表达式(在定义上,如果在含义上定义)等价于相应的强制转换表达式(5.4)。 如果类型是(可能是 cv 限定的)void 并且初始化程序是 (),则表达式是指定类型的纯右值,不执行初始化。 否则,表达式是指定类型的纯右值其结果对象使用初始化程序直接初始化(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 => 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不是算术类型,所以不是标量,所以不能零初始化,所以不能值初始化,所以不能直接初始化,所以表达式无效。

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

    N4618 3.9.1 [basic.fundamental]/9(粗体字):

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

    decltype 特别允许不完整类型:

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

    如果 decltype-specifier 的操作数是纯右值,则不应用临时实现转换 (4.4) 并且不为纯右值提供结果对象。 纯右值的类型可能不完整。


    在 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 的值作为纯右值。

    表达式 T(),其中 T 是非数组完整对象类型的 simple-type-specifiertypename-specifier 或(可能是 cv 限定的)void 类型,创建指定类型的纯右值,其value 是通过值初始化 (8.5) 类型 T 的对象产生的;没有对 void() 情况进行初始化。 [注意: 如果T 是一个非类类型,它是 cv 限定的,则在确定结果纯右值的类型时,cv 限定符会被丢弃(第 5 条) )。 ——尾注]

    类似地,simple-type-specifiertypename-specifier 后跟 braced-init-list 会创建一个临时对象使用指定的 braced-init-list 指定类型的直接列表初始化 (8.5.4),其值是作为纯右值的临时对象。

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

    【讨论】:

    • 有点晚了,但显然是一个很好的答案。谢谢你。我会尽快仔细阅读。
    • 重新阅读 C++14 版本,我认为它(“类似地”)会允许 void{} 现在明确排除。现在这对我来说更像是一个缺陷。
    猜你喜欢
    • 1970-01-01
    • 2020-10-09
    • 1970-01-01
    • 2017-01-09
    • 2012-12-09
    • 2012-12-17
    • 2015-09-26
    • 1970-01-01
    • 2011-02-10
    相关资源
    最近更新 更多