为什么 C++ 编译器对许多大括号的处理方式不同?

Fed*_*dor 6 c++ initializer-list c++20

在下面的C ++程序20我把误一个额外的一对弯曲的括号{}B{{{{A{}}}}}

#include <iostream>

struct A
{
    A() { std::cout << "A() "; }
    A( A&& ) = delete;
    ~A() { std::cout << "~A() "; }
};

struct B { std::initializer_list<A> l; };

int main()
{
    [[maybe_unused]] auto x = B{{{{A{}}}}};
    std::cout << ". ";
}
Run Code Online (Sandbox Code Playgroud)

Clang 拒绝了它,但是出现了一个奇怪的错误:

错误:调用“const A”的已删除构造函数

但令我惊讶的是 GCC 接受了它(https://gcc.godbolt.org/z/aPWe13xfc)。

你能解释一下为什么 GCC 接受它(它如何处理额外的弯曲括号)?

Dav*_*ing 4

B{\xe2\x80\xa6},由于初始值设定项列表的单个元素未指定且不是类型B(因为它根本没有类型),因此是聚合初始化 ([dcl.init.list]/3.4)。 因此是从;B::l复制初始化的 {{{A{}}}}它是 的特化std::initializer_list,因此 /3.6 和 /5 适用。const A创建一个“1 的数组”,并且{{A{}}}是其单个元素的初始值设定项。

\n

因此我们可以将代码简化为

\n
const A a = {{A{}}};\n
Run Code Online (Sandbox Code Playgroud)\n

根本没有提及B,事实上,Clang 和 GCC 对这条线产生了同样的分歧。Clang 拒绝它似乎是正确的:初始化由 /3.7 发送到构造函数,并且显然没有可行的构造函数(因此出现有关删除的移动构造函数的错误)。

\n

奇怪的是,删除此处(或原始版本中)额外的一对大括号会导致两个编译器都接受:

\n
const A a = {A{}};\n
Run Code Online (Sandbox Code Playgroud)\n

尽管事实上这A不是一个总数,所以 /3.7 仍然适用。据推测,两个编译器都过度热情地执行“保证复制省略”(尽管程度不同),将纯右值识别A{}为最终由其初始化的对象;然而,这只发生在 [dcl.init.general]/16.6.1 中,在本分析中从未发挥作用。

\n

  • @MM:那是 DIS;ISO 要求在发布之前引入更多的子条款标题(大多数称为 [*.general]),以避免段落与子条款成为同级。N4868 拥有它们,以及 C++20 之后的一些纯粹的编辑更改。 (2认同)