std::array<> 的初始化
Posted
技术标签:
【中文标题】std::array<> 的初始化【英文标题】:Initialisation of std::array<> 【发布时间】:2015-10-05 15:28:08 【问题描述】:考虑以下代码:
#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?。)
我的问题是:为什么最终的初始化会编译?
【问题讨论】:
很抱歉,我不明白为什么第一个作业无法编译,您能告诉我更多吗?这很有趣! @Ninetainedo - 查看链接的问题。 【参考方案1】:短版:以 开头的初始化子句停止大括号省略。在第一个使用
1,2
的示例中就是这种情况,但在使用A1,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
; //
因此,A1,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
inner1,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
。
【讨论】:
不错,全面的答案。谢谢! ++投票,一如既往的出色回答。不过有一个问题:N4527 之后的草案已经有了? @Columbo 好吧,不是正式的。它来自 github repo,因此它不是正式的工作草案,而是 临时工作材料。 @dyp 我确实查看了 Github 存储库,但只有 N4527(我已经使用了一段时间)。 @Columbo N4527 来自 2015-05-22,并且 git repo 包含该日期之后的提交。所以从源代码构建,你会得到某种 N4527 后的“草稿”。以上是关于std::array<> 的初始化的主要内容,如果未能解决你的问题,请参考以下文章
为啥 std::array<std::pair<int,int>, 3> 不能使用嵌套初始化列表初始化,但 std::vector<std::pair<int,in
如何使用省略尾随 '\0' 的字符串文字初始化 std::array<char, N>