std::array<> 的初始化
Initialisation of std::array<>
考虑以下代码:
#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?。)
我的问题是:为什么最后的初始化会编译?
简短版本:以 {
开头的初始化子句停止大括号省略。这是第一个使用 {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
规则如下(使用post-N4527草案,即post-C++14,但C++11无论如何都包含与此相关的缺陷):
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 members of
a subaggregate; it is erroneous for there to be more
initializer-clauses than members. 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
members of the subaggregate; any remaining initializer-clauses are
left to initialize the next member of the aggregate of which the
current subaggregate is a member.
将此应用于第一个 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
。由于第一个 initializer-clause {1,2}
以 {
开头,brace-elision exception 不会发生,并且编译器尝试用 {1,2}
初始化嵌套的 A __arr[4]
数组。剩余的 initializer-clauses {3,4}, {5,6}
等不引用 std::array
的任何子聚合,因此是非法的。
在第三个和第四个示例中,std::array
的子聚合的第一个 初始化子句 不以 [=22= 开头],因此应用大括号省略例外:
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}
导致使用所有四个 初始化程序子句 来初始化嵌套的 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
}; // }
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
}; // }
请注意大括号省略是递归应用的;我们甚至可以写出令人困惑的
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
被消耗掉了。
考虑以下代码:
#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?。)
我的问题是:为什么最后的初始化会编译?
简短版本:以 {
开头的初始化子句停止大括号省略。这是第一个使用 {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
规则如下(使用post-N4527草案,即post-C++14,但C++11无论如何都包含与此相关的缺陷):
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 members of a subaggregate; it is erroneous for there to be more initializer-clauses than members. 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 members of the subaggregate; any remaining initializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member.
将此应用于第一个 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
。由于第一个 initializer-clause {1,2}
以 {
开头,brace-elision exception 不会发生,并且编译器尝试用 {1,2}
初始化嵌套的 A __arr[4]
数组。剩余的 initializer-clauses {3,4}, {5,6}
等不引用 std::array
的任何子聚合,因此是非法的。
在第三个和第四个示例中,std::array
的子聚合的第一个 初始化子句 不以 [=22= 开头],因此应用大括号省略例外:
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}
导致使用所有四个 初始化程序子句 来初始化嵌套的 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
}; // }
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
}; // }
请注意大括号省略是递归应用的;我们甚至可以写出令人困惑的
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
被消耗掉了。