请考虑以下代码:
#include <array>
struct A
{
int a;
int b;
};
static std::array<A, 4> x1 =
{
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};
static std::array<A, 4> x2 =
{
{
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
}
};
static std::array<A, 4> x3 =
{
A{ 1, 2 },
A{ 3, 4 },
A{ 5, 6 },
A{ 7, 8 }
};
static std::array<A, 4> x4 =
{
A{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};
使用gcc编译:
$ gcc -c --std=c++11 array.cpp
array.cpp:15:1: error: too many initializers for ‘std::array<A, 4ul>’
};
^
$
NB1:注释掉第一个初始化语句,代码编译没有错误 NB2:将所有初始化转换为构造函数调用会产生相同的结果 NB3:MSVC2015表现相同。
我可以看到为什么第一次初始化无法编译,为什么第二次和第三次都没问题。 (例如,请参阅C++11: Correct std::array initialization?。)
我的问题是:为什么最终的初始化会编译?
答案 0 :(得分:28)
简短版本:以{
开头的initializer子句会停止大括号。在第一个示例中使用{1,2}
就是这种情况,但在使用A{1,2}
的第三个和第四个示例中都不是这种情况。 Brace-elision使用下一个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-clauses列表初始化成员 分组;有更多的是错误的 initializer-clauses 比成员。但是,如果初始化列表 对于subaggregate,不是以左括号开头,而是仅以左括号开头 从列表中获取足够的 initializer-clauses 来初始化 分组成员;任何剩余的初始化条款都是 左边初始化聚合的下一个成员 current subaggregate是会员。
将此应用于第一个std::array
- 示例:
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
。由于第一个初始化子句 {1,2}
以{
开头,因此大括号省略不会发生,并且编译器会尝试初始化带有A __arr[4]
的嵌套{1,2}
数组。其余的初始值设定项条款 {3,4}, {5,6}
等不会引用std::array
的任何子集合,因此是非法的。
在第三个和第四个示例中,std::array
的子聚合的第一个初始化子句不以{
开头,因此应用括号elision异常:
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
的下一个子集合。 E.g。
struct outer {
struct inner {
int a;
int b;
};
inner i;
int c;
};
outer o =
{ // o {
// i {
1, // a
2, // b
// }
3 // c
}; // }
Brace-elision使用下一个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
}; // }
请注意,brace-elision递归应用;我们甚至可以写出令人困惑的
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
和{应用大括号。消耗{1}}。