Инициализация std:: array

рассмотрим следующий код:

#include <array>

struct A
{
    int a;
    int b;
};

static std::array<A, 4> x1 =
{
        { 1, 2 },
        { 3, 4 },
        { 5, 6 },
        { 7, 8 }
};

static std::array<A, 4> x2 =
{
    {
        { 1, 2 },
        { 3, 4 },
        { 5, 6 },
        { 7, 8 }
    }
};

static std::array<A, 4> x3 =
{
       A{ 1, 2 },
       A{ 3, 4 },
       A{ 5, 6 },
       A{ 7, 8 }
};

static std::array<A, 4> x4 =
{
       A{ 1, 2 },
        { 3, 4 },
        { 5, 6 },
        { 7, 8 }
};

компиляция с gcc:

$ gcc -c --std=c++11 array.cpp
array.cpp:15:1: error: too many initializers for ‘std::array<A, 4ul>’
 };
 ^
$

NB1: комментируя первый оператор инициализации, код компилируется без ошибок.
Nb2: преобразование всех инициализаций в вызовы конструктора дает те же результаты.
NB3: MSVC2015 ведет себя так же.

Я вижу, почему первая инициализация не компилируется, и почему вторая и третья в порядке. (например, см. C++11: правильный std:: array инициализация?.)

мой вопрос: почему именно компилируется окончательная инициализация?

1 ответов


Short version: предложение инициализатора, которое начинается с { останавливает brace-elision. Это имеет место в первом примере с {1,2}, но не в третьем ни четвертом, которые используют A{1,2}. Brace-elision потребляет следующие N предложений инициализатора (где N зависит от инициализируемой совокупности), поэтому только первое предложение инициализатора N не должно начинаться с {.


во всех реализациях стандартной библиотеки C++ я знаю, std::array является структурой, которая содержит массив C-style. То есть, у вас есть совокупность, которая содержит суб-совокупность, как

template<typename T, std::size_t N>
struct array
{
    T __arr[N]; // don't access this directly!
};

при инициализации std::array С braced-init-list, поэтому вам придется инициализировать членов содержит массив. Поэтому в этих реализациях явная форма:

std::array<A, 4> x = {{ {1,2}, {3,4}, {5,6}, {7,8} }};

самый внешний набор фигурных скобок относится к std::array структура; второй набор фигурных скобок относится к вложенному массиву C-стиля.


C++ позволяет в инициализации aggregate опустить определенные фигурные скобки при инициализации вложенных агрегатов. Например:

struct outer {
    struct inner {
        int i;
    };
    inner x;
};

outer e = { { 42 } };  // explicit braces
outer o = {   42   };  // with brace-elision

правила следующие (используя проект post-N4527, который является post-C++14, но C++11 содержал дефект, связанный с этим в любом случае):

фигурные скобки можно снять в инициализатор-список следующим образом. Если инициализатор-список начинается с левой скобки, затем следует разделенный запятыми список инициализатор-п. инициализирует члены в subaggregate; это ошибочное больше инициализатор-п. чем члены. Если, однако,инициализатор-список для субагрегата не начинается с левой скобки, то только достаточно инициализатор-п. из списка берутся для инициализации члены subaggregate; оставшиеся инициализатор-п. несколько слева для инициализации следующего члена агрегата, который текущая subaggregate является членом.

применяя это к первому std::arrayпример:

static std::array<A, 4> x1 =
{
        { 1, 2 },
        { 3, 4 },
        { 5, 6 },
        { 7, 8 }
};

это интерпретируется следующим образом:

static std::array<A, 4> x1 =
{        // x1 {
  {      //   __arr {
    1,   //     __arr[0]
    2    //     __arr[1]
         //     __arr[2] = {}
         //     __arr[3] = {}
  }      //   }

  {3,4}, //   ??
  {5,6}, //   ??
  ...
};       // }

первый { берется в качестве инициализатора std::array структура. The инициализатор-п. {1,2}, {3,4} etc. затем принимаются в качестве инициализаторов subaggregates из std::array. Обратите внимание, что std::array только один subaggregate __arr. С первого инициализатор-п. {1,2} начинается с {, the скобка-исключение elision не происходит, и компилятор пытается инициализировать вложенные A __arr[4] массив {1,2}. Остальные инициализатор-п. {3,4}, {5,6} etc. не относиться к любому subaggregate из std::array и поэтому незаконны.

в третьем и четвертом примере первый инициализатор-п. для subaggregate из std::array не начинается с {, поэтому применяется исключение elision скобки:

static std::array<A, 4> x4 =
{
       A{ 1, 2 }, // does not begin with {
        { 3, 4 },
        { 5, 6 },
        { 7, 8 }
};

поэтому он интерпретируется следующим образом:

static std::array<A, 4> x4 =
  {             // x4 {
                //   __arr {       -- brace elided
    A{ 1, 2 },  //     __arr[0]
    { 3, 4 },   //     __arr[1]
    { 5, 6 },   //     __arr[2]
    { 7, 8 }    //     __arr[3]
                //   }             -- brace elided
  };            // }

отсюда A{1,2} причины все четыре инициализатор-п. будет использоваться для инициализации вложенного массива C-style. Если вы добавите другой инициализатор:

static std::array<A, 4> x4 =
{
       A{ 1, 2 }, // does not begin with {
        { 3, 4 },
        { 5, 6 },
        { 7, 8 },
       X
};

после этого X будет использоваться для инициализировать следующим subaggregate из std::array. Е. Г.

struct outer {
    struct inner {
        int a;
        int b;
    };

    inner i;
    int c;
};

outer o =
  {        // o {
           //   i {
    1,     //     a
    2,     //     b
           //   }
    3      //   c
  };       // }

Brace-elision потребляет следующие N предложений инициализатора, где N определяется количеством инициализаторов, необходимых для инициализации (sub)агрегата. Поэтому имеет значение только то, начинается ли первое из этих N предложений инициализатора с {.

больше похоже на OP:

struct inner {
    int a;
    int b;
};

struct outer {
    struct middle {
        inner i;
    };

    middle m;
    int c;
};

outer o =
  {              // o {
                 //   m {
    inner{1,2},  //     i
                 //   }
    3            //   c
  };             // }

обратите внимание, что скобка-elision применяется рекурсивно; мы даже можем написать путаница

outer o =
  {        // o {
           //   m {
           //     i {
    1,     //       a
    2,     //       b
           //     }
           //   }
    3      //   c
  };       // }

где мы опускаем обе скобки для o.m и o.m.i. Первые два предложения инициализатора используются для инициализации o.m.i, оставшийся инициализирует o.c. Как только мы вставляем пару скобок вокруг 1,2, он интерпретируется как пара фигурных скобок, соответствующих o.m:

outer o =
  {        // o {
    {      //   m {
           //     i {
      1,   //       a
      2,   //       b
           //     }
    }      //   }
    3      //   c
  };       // }

вот, инициализатор для o.m начинается с {, следовательно, скобка-elision не применяется. Инициализатор для o.m.i это 1, который не начинается с {, следовательно расчалка-elision применяется для o.m.i и два инициализатора 1 и 2 потребляются.