短版:以{ 开头的初始化子句停止大括号省略。在第一个使用{1,2} 的示例中就是这种情况,但在使用A{1,2} 的第三个或第四个示例中则不然。大括号省略会消耗接下来的 N 个初始化子句(其中 N 取决于要初始化的聚合),这就是为什么只有 N 的第一个初始化子句不能以 { 开头。
在我所知道的 C++ 标准库的所有实现中,std::array 是一个包含 C 样式数组的结构。也就是说,您有一个聚合,其中包含一个子聚合,很像
template<typename T, std::size_t N>
struct array
{
T __arr[N]; // don't access this directly!
};
当从 braced-init-list 初始化 std::array 时,您必须初始化 包含数组 的成员。因此,在这些实现中,显式形式为:
std::array<A, 4> x = {{ {1,2}, {3,4}, {5,6}, {7,8} }};
最外面的一组大括号指的是std::array结构;第二组大括号引用嵌套的 C 样式数组。
C++ 允许在初始化嵌套聚合时在聚合初始化中省略某些大括号。例如:
struct outer {
struct inner {
int i;
};
inner x;
};
outer e = { { 42 } }; // explicit braces
outer o = { 42 }; // with brace-elision
规则如下(使用N4527后的草案,即C++14后,但C++11无论如何包含与此相关的缺陷):
可以在 initializer-list 中省略大括号,如下所示。如果
initializer-list 以左大括号开头,然后是后面的
initializer-clauses 的逗号分隔列表初始化
子聚合;有更多是错误的
initializer-clauses 比成员。但是,如果 initializer-list
对于子聚合不以左大括号开头,则仅
从列表中获取足够的 initializer-clauses 来初始化
子集合的成员;任何剩余的 initializer-clauses 都是
left 来初始化聚合的下一个成员,其中
当前子聚合是成员。
将此应用于第一个std::array-example:
static std::array<A, 4> x1 =
{
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};
这解释如下:
static std::array<A, 4> x1 =
{ // x1 {
{ // __arr {
1, // __arr[0]
2 // __arr[1]
// __arr[2] = {}
// __arr[3] = {}
} // }
{3,4}, // ??
{5,6}, // ??
...
}; // }
第一个{ 被用作std::array 结构的初始化器。 initializer-clauses {1,2}, {3,4} 等然后被视为std::array 的子聚合的初始化器。请注意,std::array 只有一个子聚合 __arr。由于第一个 initializer-clause {1,2} 以 { 开头,因此 brace-elision 异常 不会发生,编译器会尝试初始化嵌套的 A __arr[4]带有{1,2} 的数组。剩余的 initializer-clauses {3,4}, {5,6} 等不引用 std::array 的任何子聚合,因此是非法的。
在第三个和第四个示例中,std::array 的子聚合的第一个 initializer-clause 不以 { 开头,因此大括号省略异常已应用:
static std::array<A, 4> x4 =
{
A{ 1, 2 }, // does not begin with {
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};
所以解释如下:
static std::array<A, 4> x4 =
{ // x4 {
// __arr { -- brace elided
A{ 1, 2 }, // __arr[0]
{ 3, 4 }, // __arr[1]
{ 5, 6 }, // __arr[2]
{ 7, 8 } // __arr[3]
// } -- brace elided
}; // }
因此,A{1,2} 导致所有四个 initializer-clauses 都被用于初始化嵌套的 C 样式数组。如果添加另一个初始化程序:
static std::array<A, 4> x4 =
{
A{ 1, 2 }, // does not begin with {
{ 3, 4 },
{ 5, 6 },
{ 7, 8 },
X
};
然后这个X 将用于初始化std::array 的下一个子聚合。例如
struct outer {
struct inner {
int a;
int b;
};
inner i;
int c;
};
outer o =
{ // o {
// i {
1, // a
2, // b
// }
3 // c
}; // }
大括号省略使用接下来的 N 个初始化子句,其中 N 是通过初始化(子)聚合所需的初始化数来定义的。因此,只有这 N 个初始化子句中的第一个是否以 { 开头才重要。
更类似于 OP:
struct inner {
int a;
int b;
};
struct outer {
struct middle {
inner i;
};
middle m;
int c;
};
outer o =
{ // o {
// m {
inner{1,2}, // i
// }
3 // c
}; // }
请注意,大括号省略是递归应用的;我们甚至可以写出令人困惑的
outer o =
{ // o {
// m {
// i {
1, // a
2, // b
// }
// }
3 // c
}; // }
我们省略了o.m 和o.m.i 的大括号。前两个初始化子句用于初始化o.m.i,其余初始化子句用于初始化o.c。一旦我们在1,2 周围插入一对大括号,它就会被解释为与o.m 对应的一对大括号:
outer o =
{ // o {
{ // m {
// i {
1, // a
2, // b
// }
} // }
3 // c
}; // }
这里,o.m 的初始化程序确实以 { 开头,因此大括号省略不适用。 o.m.i 的初始值设定项是1,它不以{ 开头,因此对o.m.i 应用大括号省略,并且消耗两个初始值设定项1 和2。