代码之家  ›  专栏  ›  技术社区  ›  skypjack

declactype(void())和declactYPe(void{})之间的差异

  •  11
  • skypjack  · 技术社区  · 8 年前

    这是问题的后续: What does the void() in decltype(void()) mean exactly? .


    解密(void()) 编译得很好 作废()
    在另一边,我注意到 decltype(void{})

    它们之间的区别是什么(在 decltype 至少)?
    为什么第二个表达式不能编译?


    int main() {
        // this doesn't compile
        //decltype(void{}) *ptr = nullptr;
        // this compiles fine
        decltype(void()) *ptr = nullptr;
        (void)ptr;
    }
    
    2 回复  |  直到 8 年前
        1
  •  5
  •   R Sahu    8 年前

    void() 与一起使用时被解释为类型id sizeof .
    作废() 与一起使用时被解释为表达式 decltype .

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

        2
  •  4
  •   TBBle    8 年前

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

    注: 我参考的是C++17或接近的答案。C++14的工作原理是一样的,文本差异在答案的末尾。

    void() [表达式类型转换] ,重点矿山:

    1安 简单型规格 (7.1.7.2)或 类型名称规范 (14.6) 后跟带括号的可选选项 表达式列表 或通过 (初始化器)构造给定初始化器的指定类型的值。如果类型是推导类类型的占位符,则在本节的其余部分,它将被重载解析为类模板推导(13.3.1.8)选择的函数的返回类型替换。

    2如果初始值设定项是一个带圆括号的单个表达式,则类型转换表达式与相应的转换表达式(5.4)等效(在dened中,如果dened在意义上)。 如果类型是(可能是cv限定的)void,初始值设定项是(),则表达式是指定类型的prvalue,不执行初始化。 否则,表达式是指定类型的prvalue,其结果对象由初始值设定项直接初始化(8.6)。对于T()形式的表达式,T不应是数组类型。

    作废() 只有在 [表达式类型转换]/2 无初始化 . void{} 不符合该异常,因此它尝试成为 直接初始化 对象

    tl;博士 此处通过C++14注释:您不能 直接初始化 void 对象

    兔子钻穿N4618 8.6 [dcl.init] 空白{}

    • 8.6/16=>; 直接初始化
    • 8.6/17.1=>; 列表已初始化
    • 8.6.4/3.10=>; 值已初始化
      • 作废() 将以8.6/11=>为上述三项设置快捷方式; 值已初始化 ,然后重新加入小径,这就是为什么 [表达式类型转换] 是必需的。
    • 8.6/8.4=>;

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

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

    4618号3.9 [基本类型]/9 定义 标量 :

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

    定义 算术类型 :

    整体和涂层类型统称为 算术类型 .

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

    除了初始化之外, 作废() 空白{} prvalue值 无效的 。即使你不能拥有 结果对象 对于不完整的类型,以及 无效的 总是 不完整:

    N4618 3.9.1号 [基本基本]/9 (粗体我的):

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

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

    N4618 7.1.7.2号 [解密类型简单]/5 (粗体我的):

    如果 脱模硬币 是prvalue,不应用临时物化转换(4.4),也没有为prvalue提供结果对象。 prvalue的类型可能不完整。


    在C++14中,N4296 5.2.3 [表达式类型转换] 措辞不同。加括号的形式几乎是加括号的版本之后才想到的:

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

    表达式T(),其中T是 simple-type-specifier typename-specifier 对于非数组完整对象类型或(可能是cv限定的) 无效的 类型,创建指定类型的prvalue,其值是通过值初始化(8.5)T类型的对象而产生的值;没有对void()案例进行初始化。[ 注: 如果 T 是cv限定的非类类型 在确定结果prvalue的类型时被丢弃(第5条)。 尾注 ]

    同样,a 简单型规格 类型名称规范 后跟一个 带括号的init列表 创建一个用specied初始化的specied类型直接列表(8.5.4)的临时对象 带括号的init列表 ,其值是作为prvalue的临时对象。

    就我们的目的而言,效果是一样的 change 与相关 P0135R1 Wording for guaranteed copy elision through simplified value categories ,它从表达式中删除了临时对象创建。相反,如果上下文需要,表达式的上下文会提供一个由表达式初始化的结果对象。

    如上所述, 解密 (不同于 sizeof typeid )没有为表达式提供结果对象,这就是为什么 作废() 即使它无法初始化结果对象,也能正常工作。


    我觉得N4618 5.2.3中的例外 [表达式类型转换] 应适用于 空白{} 也这意味着 {} 变得更复杂。参见示例 ES.23: Prefer the {} initializer syntax 在C++核心指南中,该指南目前建议 decltype(void{}) 结束 decltype(void()) . decltype(T{}) 更可能是这个咬你的地方。。。

    推荐文章