为什么在 C++14 中对数组的初始化仍然需要双括号?

Why does initialization of array of pairs still need double braces in C++14?

使用 C++14 标准,std::array 的初始化可以使用单大括号(参见 http://en.cppreference.com/w/cpp/container/array):

但是,这不适用于 std::pairstd::array

为什么这些工作:

std::pair<int, int> p { 1, 2 };
std::array<int, 3> a {1, 2, 3};

但这有效吗:

std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};

虽然这又能起作用了?

std::array<std::pair<int, int>, 3> b {{{1, 11}, {2, 22}, {3, 33}}};

此外,为了完成,一个好的旧数组的初始化确实适用于单括号

std::pair<int, int> c[3] {{1, 11}, {2, 22}, {3, 33}};

我来猜一猜
std::array<T,n> 的初始化列表应该是 T 的列表(或者可以简单地构造为 T)。所以你可以做

std::array<std::pair<int,int>,3> b { std::pair{1,11}, std::pair{2,22}, std::pair{3,33} };

但这太冗长乏味了。为了得到你想要的 std::pair<int,int> 的转换,你需要提供一个初始化列表,所以

std::array<std::pair<int,int>,3> b {
    { // element 1
      { // initialize from:
        { 1,11 } // std::initializer_list
       }
     },
  ...
};

我无法进一步辩护,但请注意 std::vector<T, Allocator>::vector( std::initializer_list<T>, const Allocator& alloc=Allocator()) 已定义,但 std::array<T,n>::array( std::initializer_list<T> ) 未定义。 std::pair<U,T>::pair( std::initializer_list<??> ) 也未定义。

理论上std::array应该用聚合初始化来初始化。所以实际上是这样的:

std::array<int, 3> a {1, 2, 3};

是一个语法糖:

std::array<int, 3> a {{1, 2, 3}};

如您所见,在第一个中,我似乎用值初始化数组,但它实际上是用花括号初始化列表进行聚合初始化。这在第二种情况下很明显。这就是初学者。

好的,为什么这不起作用?

std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};

嗯,简单地说 - 编译器无法区分您正在使用哪种类型的语法来初始化数组。 {1, 11} 可以解释为初始化列表并使用第一个版本,也可以解释为一对并与第二个版本一起使用。

此代码:

std::array<std::pair<int, int>, 3> b {{{1, 11}, {2, 22}, {3, 33}}};.

消除歧义。

来源: http://en.cppreference.com/w/cpp/language/aggregate_initialization

如果没有双大括号,该语句简直是模棱两可的。考虑以下代码:

    std::array<std::pair<int, int>, 1> a = {{ {1, 2} }};
    std::array<int, 2> b = { {1, 2} };

如果第一个定义中没有双括号,编译器会将 { {1,2} } 视为 array<int, 2> 标量初始化列表 。 您需要声明一个显式的 nested braced-init-list 以便编译器识别内部列表也是 aggregate-initialized(vs . 标量初始化),这样它就可以构造一个 std::pair.

的数组

这似乎是一个与著名的 most vexing parse 有点相似的解析歧义。我怀疑发生了什么:

如果你写

std::array<std::pair<int, int>, 3> b {{1, 11}, {2, 22}, {3, 33}};

编译器有两种解释语法的方法:

  1. 你执行全括号初始化(意思是最外层的括号指的是 std::array 的聚合初始化,而第一个最里面的括号初始化 [=13= 的内部成员表示] 这是一个真正的 C 数组)。这将无法编译,因为 std::pair<int, int> 随后不能被 1 初始化(所有大括号都用完了)。 clang 会给出一个编译器错误,准确地指出:

    error: no viable conversion from 'int' to 'std::pair<int, int>'
     std::array<std::pair<int, int>, 3> a{{1, 11}, {2, 22}, {3, 33}};
                                              ^
    

    另请注意,如果没有要初始化的内部成员聚合,则此问题已解决,即

    std::pair<int, int> b[3] = {{1, 11}, {2, 22}, {3, 33}};
    

    将像聚合初始化一样编译得很好。

  2. (你的意思。)你执行了一个省略大括号的初始化,因此最里面的大括号用于各个对的聚合初始化,而省略了内部数组表示的大括号.请注意,即使不存在这种歧义,正如 中正确指出的那样,大括号省略规则也不适用,因为 std::pair 不是聚合类型,因此程序仍然是病式的。

编译器会更喜欢选项 1。通过提供额外的大括号,您可以执行全大括号初始化并消除任何语法歧义。

C++14 brace elision rule 仅适用于子聚合初始化。

因此,例如这样的工作:

std::array<std::array<int, 3>, 3> a{1, 11, 2, 22, 3, 33};

此处聚合的聚合可以列表初始化,无需额外的大括号。

但是 std::pair 不是 aggregate(它有构造函数),所以规则不适用。

这意味着如果没有大括号省略规则,std::array本身就是一个内部有数组的聚合,需要额外的一组大括号才能被列表初始化.请记住,class 模板 array 实现为:

template<typename T, std::size_t N> 
struct array {
  T elems[N];
};

为了 list-initialize 它没有大括号省略规则,你需要一组额外的大括号才能到达 elems 成员。