在初始化列表中,外层大括号什么时候可以省略?

54

我在使用VC2010编译以下代码时遇到了C2078错误。

struct A
  {
  int foo;
  double bar;
  };

std::array<A, 2> a1 = 
  // error C2078: too many initializers
  {
    {0, 0.1},
    {2, 3.4}
  };

// OK
std::array<double, 2> a2 = {0.1, 2.3};

我发现a1的正确语法是:

std::array<A, 2> a1 = 
  {{
    {0, 0.1},
    {2, 3.4}
  }};
问题是:为什么在a1中需要额外的花括号,而在a2中不需要?
更新
问题似乎与std::array无关。以下是一些例子:
struct B
  {
  int foo[2];
  };

// OK
B meow1 = {1,2};
B bark1 = {{1,2}};

struct C
  {
  struct 
    { 
    int a, b; 
    } foo;
  };

// OK
C meow2 = {1,2};
C bark2 = {{1,2}};

struct D
  {
  struct 
    { 
    int a, b; 
    } foo[2];
  };

D meow3 = {{1,2},{3,4}};  // error C2078: too many initializers
D bark3 = {{{1,2},{3,4}}};

我仍然不明白为什么struct D会出错而B和C不会。


1
@KerrekSB:因为double不是一个聚合体,而A是。换句话说,std::array<double, 2>是一个聚合体的聚合体,而std::array<A, 2>是一个聚合体的聚合体的聚合体! - Nawaz
1
@Nawaz:a2 的完全标准一致语法是 std::array<double, 2> a2 = {{0.1, 2.3}};,但我也想知道为什么允许省略一对大括号。 - Andriy
@Andrey:这就是我说的。+1 - Nawaz
1
@fish 不行,std::array 是一个聚合类型,因此它不能有这样的构造函数。问题在于它可能被实现为只有一个数组作为元素,这就是为什么需要额外的大括号的原因。 - juanchopanza
这里有一个有趣的相关帖子链接 - juanchopanza
显示剩余7条评论
1个回答

68
额外的大括号是必需的,因为std::array是一个聚合体和POD,不同于标准库中的其他容器。 std::array没有用户定义的构造函数。它的第一个数据成员是大小为N的数组(您将其作为模板参数传递),并且该成员直接使用初始化程序进行初始化。 额外的大括号用于直接初始化的内部数组。
情况与以下情况相同:
//define this aggregate - no user-defined constructor
struct Aarray
{
   A data[2];  //data is an internal array
};

你该如何初始化这个变量?如果你这样做:

Aarray a1 =
{
   {0, 0.1},
   {2, 3.4}
};

出现了编译错误compilation error:

错误: 'Aarray' 的初始值设定项太多

如果你使用GCC编译,std::array会产生相同的错误。

因此,正确的做法是使用大括号,如下所示:

Aarray a1 =
{
  {  //<--this tells the compiler that initialization of `data` starts

        { //<-- initialization of `data[0]` starts

           0, 0.1

        }, //<-- initialization of `data[0]` ends

       {2, 3.4}  //initialization of data[1] starts and ends, as above

  } //<--this tells the compiler that initialization of `data` ends
};

这段代码编译良好。再次强调,额外的花括号是必须的,因为你正在初始化内部数组。

--

现在的问题是为什么在double的情况下不需要额外的大括号? 这是因为double不是一个聚合类型,而A是。换句话说,std::array<double, 2>是一个聚合的聚合类型,而std::array<A, 2>是一个聚合的聚合的聚合类型1 1. 我认为在double的情况下也仍然需要额外的大括号(例如this),才能完全符合标准,但是代码可以在没有它们的情况下工作。看来我需要再次查阅规范!

关于大括号和额外的大括号

我查阅了规范。这个章节(来自C++11的§8.5.1/11)非常有趣,并适用于这种情况:

在形式为

T x = { a };

“花括号可以在初始化列表中省略,方法如下。如果初始化列表以左花括号开头,则随后用逗号分隔的初始化子句列表将初始化子聚合体的成员;初始化子句的数量不能超过成员数量,否则会出错。但是,如果子聚合体的初始化列表不以左花括号开头,则只需要从列表中取足够的初始化子句来初始化子聚合体的成员;任何剩余的初始化子句将用于初始化当前子聚合体所属的聚合体的下一个成员。 [示例:”
float y[4][3] = {
{ 1, 3, 5 },
{ 2, 4, 6 },
{ 3, 5, 7 },
};

这是一个完全支撑的初始化:1、3和5初始化了数组y[0]的第一行,即y[0][0]、y[0][1]和y[0][2]。同样,接下来的两行初始化了y[1]和y[2]。初始化程序提前结束,因此y[3]的元素被初始化为0.0。在下面的例子中,初始化列表中的大括号被省略了;但是初始化列表具有与上面示例中完全支撑的初始化列表相同的效果。
float y[4][3] = {
1, 3, 5, 2, 4, 6, 3, 5, 7
};

根据我从上述引用中理解的内容,我可以说应该允许以下操作:y的初始化以左大括号开始,但是y[0]的初始化没有,因此列表中的三个元素被使用。同样,接下来的三个元素连续用于y[1]和y[2]。——示例结束]
//OKAY. Braces are completely elided for the inner-aggregate
std::array<A, 2> X =   
{
  0, 0.1,
  2, 3.4
};

//OKAY. Completely-braced initialization
std::array<A, 2> Y = 
{{
   {0, 0.1},
   {2, 3.4}
}};

在第一个示例中,内部聚合体的大括号被完全省略,而第二个示例具有完全加括号的初始化。在您的情况下(即double的情况下),初始化使用第一种方法(内部聚合体的大括号被完全省略)。
但这应该是不允许的:
//ILL-FORMED : neither braces-elided, nor fully-braced
std::array<A, 2> Z = 
{
  {0, 0.1},
  {2, 3.4}
};

它既不是省略了大括号的初始化,也没有足够的大括号来进行完全初始化。因此,它是不合法的。

1
这里有一篇有趣的相关文章链接。对我来说,数组必须作为单元素聚合体实现并不清楚。标准提供了一个示例实现以强调它是一个聚合体,但我没有看到必须这样实现的要求(尽管我目前想不出其他的实现方式)。 - juanchopanza
1
太棒了!刚刚从问题中找到了更多可编译的struct D的初始化器:D d1 = {1,2,3,4}; D d2 = {{1,2,3,4}}; - Andriy
3
对于那些没有点击@juanchopanza发布的链接的人,这里有一个关于花括号省略限制的官方缺陷报告的链接。 - Tamás Szelei
1
类型不必是POD,才能应用花括号省略。此外,像std::array<std::string>这样的东西显然不是POD。 - Luc Danton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接