子聚合的指定初始化程序是否需要花括号?

Does designated initializer of sub-aggregate require curly braces?

在下面的程序中,聚合结构 B 具有字段 a,它本身就是一个聚合。是否可以使用 C++20 指定初始化器来设置其值而不用花括号括起来?

struct A { int i; };
struct B { A a; };

int main() { 
    [[maybe_unused]] B x{1}; //ok everywhere
    [[maybe_unused]] B y{.a = {1}}; //ok everywhere
    [[maybe_unused]] B z{.a = 1}; //ok in MSVC,Clang; error in GCC
}

MSVC 和 Clang 编译器接受此代码。但是 GCC 发出一个奇怪的错误:

error: 'A' has no non-static data member named 'a'

演示:https://gcc.godbolt.org/z/65j1sTcPG

是GCC的bug,还是标准不允许这样的初始化?

GCC 拒绝此代码是正确的:它尝试从1复制初始化z.a([dcl.init.aggr]/4.2 ),正如评论所说,这是行不通的。然而,GCC 似乎设想产生诊断的大括号省略是无效的:指定初始化列表不存在。

TLDR; GCC 是对的,其他人都错了,因为他们一直假装指定的初始化列表就像等效的非指定初始化列表一样。


要了解这里发生了什么(以及为什么编译器不同意),让我们看一下您的第一个示例:

B x{1};

由于我们使用大括号,rules of list initialization kick in. The list is not a designated initializer list, so 3.1 fails. 3.2 fails because int is not of type B or a type derived from B. 3.3 fails fails because B isn't an array of characters. Finally 3.4 is followed, which takes us to aggregate initialization.

[dcl.init.aggr]/3.2 tells us B 的显式初始化元素由 B::a.

组成

第 4 段告诉我们显式初始化的元素是如何初始化的。 4.1 不适用,因为 B 不是 union。但也... 4.2 不适用,因为 B::a 无法从 1.

复制初始化

这似乎不应该起作用。幸运的是,paragraphs 15 and 16 exist:

Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the elements of a subaggregate; it is erroneous for there to be more initializer-clauses than elements. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializer-clauses from the list are taken to initialize the elements of the subaggregate; any remaining initializer-clauses are left to initialize the next element of the aggregate of which the current subaggregate is an element.

All implicit type conversions ([conv]) are considered when initializing the element with an assignment-expression. If the assignment-expression can initialize an element, the element is initialized. Otherwise, if the element is itself a subaggregate, brace elision is assumed and the assignment-expression is considered for the initialization of the first element of the subaggregate.

也就是说,如果初始化程序无法通过复制初始化来初始化 A,则大括号省略规则开始生效。并且 A 可以 进行初始化来自 {1} 的初始化列表。因此,它是。

这就是指定初始化器有问题的地方。 designated-initializer-list 而不是 initializer-list。因此,大括号省略段落 不适用

因此,B z{.a = 1}; 一定会失败。

其他编译器没有捕捉到这一点的原因可能如下。他们可能通过剥离指定并在非连续元素之间插入任何默认成员 initializers/value 初始化来实现指定的初始值设定项,然后应用正常的聚合初始值设定项规则。但这并不完全相同,因为指定的初始化列表不参与大括号省略。