Search code examples
c++initializationlanguage-lawyerstdarrayaggregate-initialization

Initialize std::array using element type from the declaration implicitly


In the following code I'm trying to initialize arrays (raw C-classic and std::array) minimizing the usage of the element type, i.e. S:

#include <array>

struct S { unsigned u; int i; };

auto std_array = std::array<S, 3>{
    S{0U, 0},
    S{1U, 1},
    S{2U, 2}
};

S raw_array[] = {
    {0U, 0},
    {1U, 1},
    {2U, 2}
}; 

/*  // compile error: excess elements in struct initializer
std::array<S,3> std_array_no_type = {
    {0U, 0},
    {1U, 1},
    {2U, 2}
}; 
*/

std::array<S,3> std_array_one_type_only = {
    S{0U, 0},
    {1U, 1},
    {2U, 2}
}; 

int main() {}

Using the raw_array I can specify S only once. But trying the same with std::array doesn't work (see the commented std_array_no_type). I have to specify S type for each or (this is also an interesting part of the question) only for the first element in the initializer list (see std_array_one_type_only).

So, is there a way to define an initialized std::array object using the type S only once? If no, according to which clause of the standard? And why single explicit type S allows std_array_one_type_only to be compiled?


Solution

  • About the 1st question,

    So, is there a way to define an initialized std::array object using the type S only once?

    You can add another pair of {}, because std::array contains an underlying array in fact.

    std::array<S,3> std_array_one_type_only = {{{0U, 0}, {1U, 1}, {2U, 2}}}; 
    //                                        ^                           ^ for std::array
    //                                         ^                         ^  for underlying array
    //                                          ^     ^                     for the 1st element of underlying array
    //                                                   ^     ^            for the 2nd element of underlying array
    //                                                            ^     ^   for the 3rd element of underlying array
    

    About the 2nd question,

    And why single explicit type S allows std_array_one_type_only to be compiled?

    In aggregate initialization, the braces for nested initializer lists may be omitted,

    If the aggregate initialization uses copy- (until C++14)list-initialization syntax (T a = {args..} or T a {args..} (since C++14)), the braces around the nested initializer lists may be elided (omitted), in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, and the subsequent initializer clauses are used to initialize the following members of the object.

    Given

    std::array<S,3> std_array_no_type = {{0U, 0}, {1U, 1}, {2U, 2}}; 
    //                                  ^                         ^  for std::array
    //                                   ^     ^                     for underlying array
    

    The 1st {0U, 0} would be tried to be used to initialize the whole underlying array, then leads to the error like excess elements in initializer because std::array doesn't contain any further subobjects.

    Given

    std::array<S,3> std_array_no_type = {S{0U, 0}, {1U, 1}, {2U, 2}}; 
    //                                  ^                          ^ for std::array
    //                                    ^     ^                    for the 1st element of underlying array
    //                                             ^     ^           for the 2nd element of underlying array
    //                                                      ^     ^  for the 3rd element of underlying array
    

    S{0U, 0} can't be used to initialize the underlying array (which doesn't satisfy the style of aggregate initialization), then it'll be used to initialize the 1st element of the underlying array, i.e. the above braces-omitting rule gets applied, then the following {1U, 1} and {2U, 2} are used to initialize the following members of the underlying array.