std::array 的嵌套聚合初始化
Nested aggregate initialization of std::array
我想知道,为什么下面代码中 std_arr
的声明会产生错误,而 c_arr
编译得很好:
struct S { int a, b; };
S c_arr[] = {{1, 2}, {3, 4}}; // OK
std::array<S, 2> std_arr = {{1, 2}, {3, 4}}; // Error: too many initializers
std::array
和S
都是聚合。来自 aggregate initialization on cppreference.com:
If the initializer clause is a nested braced-init-list (which is not an expression and has no type), the corresponding class member is
itself an aggregate: aggregate initialization is recursive.
为什么 std::array
的这个初始化不编译?
聚合初始化中的大括号大部分是可选的,所以你可以这样写:
S c_arr[] = {1, 2, 3, 4}; // OK
std::array<S, 2> std_arr = {1, 2, 3, 4}; // OK
但是,如果您确实添加了大括号,则大括号将应用于下一个子对象。不幸的是,当您开始嵌套时,这会导致愚蠢的代码有效,而像您这样的明智代码无效。
std::array<S, 2> std_arr = {{1, 2, 3, 4}}; // OK
std::array<S, 2> std_arr = {1, 2, {3, 4}}; // OK
std::array<S, 2> std_arr = {1, {2}, {3, 4}}; // OK
这些都可以。 {1, 2, 3, 4}
是 std_arr
的 S[2]
成员的有效初始化程序。 {2}
没问题,因为它试图初始化一个 int
,而 {2}
是一个有效的初始化器。 {3, 4}
被用作 S
的初始化器,它也适用于
std::array<S, 2> std_arr = {{1, 2}, {3, 4}}; // error
这不行,因为 {1, 2}
被视为 S[2]
成员的有效初始化程序。其余 int
个子对象初始化为零。
然后您有 {3, 4}
,但没有更多成员需要初始化。
正如评论中指出的那样,
std::array<S, 2> std_arr = {{{1, 2}, {3, 4}}};
也有效。嵌套的 {{1, 2}, {3, 4}}
是 S[2]
成员的初始化程序。 {1, 2}
是第一个 S
元素的初始化程序。 {3, 4}
是第二个 S
元素的初始化程序。
我在这里假设 std::array<S, 2>
包含类型为 S[2]
的数组成员,它在当前实现中确实如此,我相信这很可能会得到保证,但之前已在 SO 中涵盖,目前无法保证。
由于问题被标记为 C++14,我将引用 N4140。在 [array] 中,它表示 std::array
是一个聚合:
2 An array
is an aggregate (8.5.1) that can be initialized with the
syntax
array a = { initializer-list };
where initializer-list is a comma-separated list of up to N
elements
whose types are convertible to T
.
一般来说,大家一致认为需要一对额外的外括号来初始化底层聚合,看起来有点像T elems[N]
。在第 3 段中,解释说这是为了说明目的,实际上并不是界面的一部分。然而,在实践中,libstdc++ 和 Clang 是这样实现的:
template<typename _Tp, std::size_t _Nm>
struct __array_traits
{
typedef _Tp _Type[_Nm];
static constexpr _Tp&
_S_ref(const _Type& __t, std::size_t __n) noexcept
{ return const_cast<_Tp&>(__t[__n]); }
};
template<typename _Tp, std::size_t _Nm>
struct array
{
/* Snip */
// Support for zero-sized arrays mandatory.
typedef _GLIBCXX_STD_C::__array_traits<_Tp, _Nm> _AT_Type;
typename _AT_Type::_Type _M_elems;
叮当声:
template <class _Tp, size_t _Size>
struct _LIBCPP_TYPE_VIS_ONLY array
{
/* Snip */
value_type __elems_[_Size > 0 ? _Size : 1];
C++11 和 C++14 之间在聚合初始化方面有一些变化,但是 none 这会导致:
std::array<S, 2> std_arr = {{1, 2}, {3, 4}};
不是病式。
我想知道,为什么下面代码中 std_arr
的声明会产生错误,而 c_arr
编译得很好:
struct S { int a, b; };
S c_arr[] = {{1, 2}, {3, 4}}; // OK
std::array<S, 2> std_arr = {{1, 2}, {3, 4}}; // Error: too many initializers
std::array
和S
都是聚合。来自 aggregate initialization on cppreference.com:
If the initializer clause is a nested braced-init-list (which is not an expression and has no type), the corresponding class member is itself an aggregate: aggregate initialization is recursive.
为什么 std::array
的这个初始化不编译?
聚合初始化中的大括号大部分是可选的,所以你可以这样写:
S c_arr[] = {1, 2, 3, 4}; // OK
std::array<S, 2> std_arr = {1, 2, 3, 4}; // OK
但是,如果您确实添加了大括号,则大括号将应用于下一个子对象。不幸的是,当您开始嵌套时,这会导致愚蠢的代码有效,而像您这样的明智代码无效。
std::array<S, 2> std_arr = {{1, 2, 3, 4}}; // OK
std::array<S, 2> std_arr = {1, 2, {3, 4}}; // OK
std::array<S, 2> std_arr = {1, {2}, {3, 4}}; // OK
这些都可以。 {1, 2, 3, 4}
是 std_arr
的 S[2]
成员的有效初始化程序。 {2}
没问题,因为它试图初始化一个 int
,而 {2}
是一个有效的初始化器。 {3, 4}
被用作 S
的初始化器,它也适用于
std::array<S, 2> std_arr = {{1, 2}, {3, 4}}; // error
这不行,因为 {1, 2}
被视为 S[2]
成员的有效初始化程序。其余 int
个子对象初始化为零。
然后您有 {3, 4}
,但没有更多成员需要初始化。
正如评论中指出的那样,
std::array<S, 2> std_arr = {{{1, 2}, {3, 4}}};
也有效。嵌套的 {{1, 2}, {3, 4}}
是 S[2]
成员的初始化程序。 {1, 2}
是第一个 S
元素的初始化程序。 {3, 4}
是第二个 S
元素的初始化程序。
我在这里假设 std::array<S, 2>
包含类型为 S[2]
的数组成员,它在当前实现中确实如此,我相信这很可能会得到保证,但之前已在 SO 中涵盖,目前无法保证。
由于问题被标记为 C++14,我将引用 N4140。在 [array] 中,它表示 std::array
是一个聚合:
2 An
array
is an aggregate (8.5.1) that can be initialized with the syntaxarray a = { initializer-list };where initializer-list is a comma-separated list of up to
N
elements whose types are convertible toT
.
一般来说,大家一致认为需要一对额外的外括号来初始化底层聚合,看起来有点像T elems[N]
。在第 3 段中,解释说这是为了说明目的,实际上并不是界面的一部分。然而,在实践中,libstdc++ 和 Clang 是这样实现的:
template<typename _Tp, std::size_t _Nm>
struct __array_traits
{
typedef _Tp _Type[_Nm];
static constexpr _Tp&
_S_ref(const _Type& __t, std::size_t __n) noexcept
{ return const_cast<_Tp&>(__t[__n]); }
};
template<typename _Tp, std::size_t _Nm>
struct array
{
/* Snip */
// Support for zero-sized arrays mandatory.
typedef _GLIBCXX_STD_C::__array_traits<_Tp, _Nm> _AT_Type;
typename _AT_Type::_Type _M_elems;
叮当声:
template <class _Tp, size_t _Size>
struct _LIBCPP_TYPE_VIS_ONLY array
{
/* Snip */
value_type __elems_[_Size > 0 ? _Size : 1];
C++11 和 C++14 之间在聚合初始化方面有一些变化,但是 none 这会导致:
std::array<S, 2> std_arr = {{1, 2}, {3, 4}};
不是病式。