Initialisation of std::array<>

前端 未结 1 617
面向向阳花
面向向阳花 2021-02-04 07:07

Consider the following code:

#include 

struct A
{
    int a;
    int b;
};

static std::array x1 =
{
        { 1, 2 },
        { 3, 4 }         


        
相关标签:
1条回答
  • 2021-02-04 07:28

    Short version: An initializer-clause that starts with { stops brace-elision. This is the case in the first example with {1,2}, but not in the third nor fourth which use A{1,2}. Brace-elision consumes the next N initializer-clauses (where N is dependent on the aggregate to be initialized), which is why only the first initializer-clause of the N must not begin with {.


    In all implementations of the C++ Standard Library I know of, std::array is a struct which contains a C-style array. That is, you have an aggregate which contains a sub-aggregate, much like

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

    When initializing a std::array from a braced-init-list, you'll therefore have to initialize the members of the contained array. Therefore, on those implementations, the explicit form is:

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

    The outermost set of braces refers to the std::array struct; the second set of braces refers to the nested C-style array.


    C++ allows in aggregate initialization to omit certain braces when initializing nested aggregates. For example:

    struct outer {
        struct inner {
            int i;
        };
        inner x;
    };
    
    outer e = { { 42 } };  // explicit braces
    outer o = {   42   };  // with brace-elision
    

    The rules are as follows (using a post-N4527 draft, which is post-C++14, but C++11 contained a defect related to this anyway):

    Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the members of a subaggregate; it is erroneous for there to be more initializer-clauses than members. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializer-clauses from the list are taken to initialize the members of the subaggregate; any remaining initializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member.

    Applying this to the first std::array-example:

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

    This is interpreted as follows:

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

    The first { is taken as the initializer of the std::array struct. The initializer-clauses {1,2}, {3,4} etc. then are taken as the initializers of the subaggregates of std::array. Note that std::array only has a single subaggregate __arr. Since the first initializer-clause {1,2} begins with a {, the brace-elision exception does not occur, and the compiler tries to initialize the nested A __arr[4] array with {1,2}. The remaining initializer-clauses {3,4}, {5,6} etc. do not refer to any subaggregate of std::array and are therefore illegal.

    In the third and fourth example, the first initializer-clause for the subaggregate of std::array does not begin with a {, therefore the brace elision exception is applied:

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

    So it is interpreted as follows:

    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
      };            // }
    

    Hence, the A{1,2} causes all four initializer-clauses to be consumed to initialize the nested C-style array. If you add another initializer:

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

    then this X would be used to initialize the next subaggregate of std::array. E.g.

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

    Brace-elision consumes the next N initializer-clauses, where N is defined via the number of initializers required for the (sub)aggregate to be initialized. Therefore, it only matters whether or not the first of those N initializer-clauses starts with a {.

    More similarly to the 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
      };             // }
    

    Note that brace-elision applies recursively; we can even write the confusing

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

    Where we omit both the braces for o.m and o.m.i. The first two initializer-clauses are consumed to initialize o.m.i, the remaining one initializes o.c. Once we insert a pair of braces around 1,2, it is interpreted as the pair of braces corresponding to o.m:

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

    Here, the initializer for o.m does start with a {, hence brace-elision does not apply. The initializer for o.m.i is 1, which does not start with a {, hence brace-elision is applied for o.m.i and the two initializers 1 and 2 are consumed.

    0 讨论(0)
提交回复
热议问题