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::arrayS都是聚合。来自 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_arrS[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}};

不是病式。