Jer*_*emy 15 c++ aggregate-initialization c++11 stdarray
请考虑以下代码:
#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 }
};
Run Code Online (Sandbox Code Playgroud)
用gcc编译:
$ gcc -c --std=c++11 array.cpp
array.cpp:15:1: error: too many initializers for ‘std::array<A, 4ul>’
};
^
$
Run Code Online (Sandbox Code Playgroud)
NB1:注释掉第一个初始化语句,代码编译没有错误.
NB2:将所有初始化转换为构造函数调用会产生相同的结果.
NB3:MSVC2015表现相同.
我可以看到为什么第一次初始化无法编译,为什么第二次和第三次都没问题.(例如,参见C++ 11:正确的std :: array初始化?)
我的问题是:为什么最终的初始化编译完全正确?
dyp*_*dyp 28
简短版本:一个以{stops括号开头的startizer 子句.这是第一个例子中的情况{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!
};
Run Code Online (Sandbox Code Playgroud)
std::array从braced-init-list初始化a时,您必须初始化包含的数组的成员.因此,在这些实现上,显式形式是:
std::array<A, 4> x = {{ {1,2}, {3,4}, {5,6}, {7,8} }};
Run Code Online (Sandbox Code Playgroud)
最外面的大括号是指std::array结构; 第二组大括号是指嵌套的C风格数组.
在初始化嵌套聚合时,C++允许聚合初始化省略某些大括号.例如:
struct outer {
struct inner {
int i;
};
inner x;
};
outer e = { { 42 } }; // explicit braces
outer o = { 42 }; // with brace-elision
Run Code Online (Sandbox Code Playgroud)
规则如下(使用后N4527草案,这是后C++ 14,但C++ 11包含与此相关的缺陷):
可以在初始化列表中省略大括号,如下所示.如果 初始化列表以左括号开头,则后续的逗号分隔的initializer-clause列表初始化子集合的成员; 如果有比成员更多的初始化子条款是错误的 .但是,如果 子聚合的初始化列表不以左括号开头,则只从列表中获取足够的初始化子句来初始化子聚合的成员; 剩下的任何初始化子句用于初始化当前子聚合为成员的聚合的下一个成员.
将此应用于第一个std::array示例:
static std::array<A, 4> x1 =
{
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};
Run Code Online (Sandbox Code Playgroud)
这解释如下:
static std::array<A, 4> x1 =
{ // x1 {
{ // __arr {
1, // __arr[0]
2 // __arr[1]
// __arr[2] = {}
// __arr[3] = {}
} // }
{3,4}, // ??
{5,6}, // ??
...
}; // }
Run Code Online (Sandbox Code Playgroud)
第一个{被作为std::array结构的初始化器.然后将initializer-clause {1,2}, {3,4}等作为子聚合的初始化器std::array.请注意,std::array只有一个子聚合__arr.由于第一个初始化子句 {1,2}以a开头{,因此不会发生大括号扩展异常,编译器会尝试初始化嵌套A __arr[4]数组{1,2}.其余的初始化条款 {3,4}, {5,6}等不涉及任何子集合,std::array因此是非法的.
在第三个和第四个示例中,subaggregate 的第一个initializer子句std::array 不以a开头{,因此应用了大括号elision异常:
static std::array<A, 4> x4 =
{
A{ 1, 2 }, // does not begin with {
{ 3, 4 },
{ 5, 6 },
{ 7, 8 }
};
Run Code Online (Sandbox Code Playgroud)
所以解释如下:
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
}; // }
Run Code Online (Sandbox Code Playgroud)
因此,A{1,2}导致所有四个初始化子句被用来初始化嵌套的C样式数组.如果添加其他初始值设定项:
static std::array<A, 4> x4 =
{
A{ 1, 2 }, // does not begin with {
{ 3, 4 },
{ 5, 6 },
{ 7, 8 },
X
};
Run Code Online (Sandbox Code Playgroud)
那么这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
}; // }
Run Code Online (Sandbox Code Playgroud)
Brace-elision使用下一个N初始化子句,其中N是通过初始化(子)聚合所需的初始化程序的数量来定义的.因此,只关注N个初始化子句中的第一个是否以a开头{.
更类似于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
}; // }
Run Code Online (Sandbox Code Playgroud)
请注意,brace-elision递归应用; 我们甚至可以写出令人困惑的东西
outer o =
{ // o {
// m {
// i {
1, // a
2, // b
// }
// }
3 // c
}; // }
Run Code Online (Sandbox Code Playgroud)
在这里我们忽略了这两个大括号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
}; // }
Run Code Online (Sandbox Code Playgroud)
这里,初始化程序o.m确实以a开头{,因此不再适用括号.o.m.iis 的初始化程序1,不是以a开头{,因此使用了大括号o.m.i和两个初始化程序1并被2消耗.