std::array 的嵌套聚合初始化

Posted

技术标签:

【中文标题】std::array 的嵌套聚合初始化【英文标题】:Nested aggregate initialization of std::array [duplicate] 【发布时间】:2015-04-16 23:13:17 【问题描述】:

我想知道,为什么在下面的代码中声明 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:

如果初始化子句是一个嵌套的braced-init-list(它不是一个表达式并且没有类型),那么对应的类成员是 本身就是一个聚合:聚合初始化是递归的。

为什么std::array 的这个初始化不能编译?

【问题讨论】:

它应该是 std::array&lt;S, 2&gt; std_arr 1, 2, 3, 4 ; - 围绕构造函数参数的外部参数,初始化列表的下一对,每个 S 元素的内部对。 C++14 将使它与更少的一组外部 一起工作。 (= 是可选的。) @remyabel 聚合初始化的规则在 C++11 和 C++14、IIRC 之间发生了变化,我不清楚它们在这里不相关。 @hvd 不过大括号省略没有变化。 你说:“嘿,我读了标准,它说我应该能够做 X,但是当我做我的编译器时会哭”。您不认为指定编译器和您正在使用的版本可能相关吗? @remyabel 啊,这个几乎重复的答案给出了答案:大括号省略在 C++11 和 C++14 之间没有变化,但在后 C 中确实发生了变化应用于 C++11 的 ++11 个 DR。因此,您在 -std=c++11-std=c++14 的编译器中看不到差异是正确的:即使在 C++11 模式下也应用了 DR 分辨率。除此之外,它涵盖了std::array&lt;S, 2&gt; std_arr 1, 2, 3, 4; 没有=,因此无论如何它与这个特定问题无关。 :) 【参考方案1】:

聚合初始化中的大括号在很大程度上是可选的,所以你可以这样写:

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, 4std_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,但没有更多的成员要初始化。

正如 cmets 中所指出的,

std::array<S, 2> std_arr = 1, 2, 3, 4;

也可以。嵌套的1, 2, 3, 4S[2] 成员的初始化程序。 1, 2 是第一个 S 元素的初始化程序。 3, 4 是第二个 S 元素的初始化器。

我在这里假设std::array&lt;S, 2&gt; 包含S[2] 类型的数组成员,它在当前实现中执行,并且我相信它可能会得到保证,但之前已经在 SO并且目前无法保证。

【讨论】:

我现在看到了。合乎逻辑,但非常不明显。谢谢。 如果你想省略 b 的初始化器;例如std::array&lt;S, 2&gt; g = 1, 3 ; 希望得到g[1].a == 3 ...然后似乎所有奇怪的建议都无法使用,双支撑是唯一的选择。对于 C++14 来说就这么多了,据说 std::array 不需要双支撑 ...【参考方案2】:

由于问题标记为 C++14,我将引用 N4140。在 [array] 中,它说 std::array 是一个聚合:

2 array 是一个聚合 (8.5.1),可以使用 语法

 数组 a =  initializer-list ;

其中 initializer-list 是一个逗号分隔的列表,最多包含 N 个元素 其类型可转换为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 之间在聚合初始化方面有一些变化,但没有任何变化:

std::array<S, 2> std_arr = 1, 2, 3, 4;

格式不正确。

【讨论】:

以上是关于std::array 的嵌套聚合初始化的主要内容,如果未能解决你的问题,请参考以下文章

在 g++ 上进行聚合初始化的 std::array 会生成大量代码

为啥 std::array<std::pair<int,int>, 3> 不能使用嵌套初始化列表初始化,但 std::vector<std::pair<int,in

std::array 初始化中的大括号省略

初始化 std::array 的 std::array

std::array 默认初始化还是值初始化?

我应该如何大括号初始化 std::pairs 的 std::array?