How do I initialize a member array with an initializer_list?

43,232

Solution 1

You can use a variadic template constructor instead of an initializer list constructor:

struct foo { 
    int x[2]; 
    template <typename... T> 
    foo(T... ts) : x{ts...} { // note the use of brace-init-list
    } 
};

int main() {
    foo f1(1,2);   // OK
    foo f2{1,2};   // Also OK
    foo f3(42);    // OK; x[1] zero-initialized
    foo f4(1,2,3); // Error: too many initializers
    foo f5(3.14);  // Error: narrowing conversion not allowed
    foo f6("foo"); // Error: no conversion from const char* to int
}

EDIT: If you can live without constness, another way would be to skip initialization and fill the array in the function body:

struct foo {
    int x[2]; // or std::array<int, 2> x;
    foo(std::initializer_list<int> il) {
       std::copy(il.begin(), il.end(), x);
       // or std::copy(il.begin(), il.end(), x.begin());
       // or x.fill(il.begin());
    }
}

This way, though, you lose the compile-time bounds checking that the former solution provides.

Solution 2

As far as I can tell, using list-initialization of the function argument of the constructor (8.5.4/1) should be legal and solves many of the issues of the above. However, GCC 4.5.1 on ideone.com fails to match the constructor and rejects it.

#include <array>

struct Foo
  {
    std::array< int, 2 > const data;

    Foo(std::array<int, 2> const& ini) // parameter type specifies size = 2
    : data( ini )
    {}
  };

Foo f( {1,3} ); // list-initialize function argument per 8.5.4/1

If you really insist on initializer_list, you can use reinterpret_cast to turn the underlying array of the initializer_list into a C-style array.

Foo(std::initializer_list<int> ini) // pass without reference- or cv-qualification
: data( reinterpret_cast< std::array< int, 2 > const & >( * ini.begin() )

Solution 3

According to the discussion here:

the right syntax for Potatoswatter's second solution is:

Foo f( {{1,3}} ); //two braces

a little bit ugly, not consistent with common usage

Solution 4

Just a small addition to great JohannesD answer.

In case of no arguments passed to foo constructor, array will be default initialized. But sometimes you want to keep underlying array uninitilized (maybe for performance reasons). You cannot add default constructor along with variadic-templated one. Workaround is additional argument to variadic-templated constructor, to distinguish it from zero-argument constructor:

template<class T, size_t rows, size_t cols>
class array2d
{
    std::array<T, rows * cols> m_Data;
    public:

    array2d() {}

    template <typename T, typename... Types>
    array2d(T t, Types... ts) : m_Data{ { t, ts... } } {}
};

So, now you can brace-initilize object, or left it uninitialized:

array2d<int, 6, 8> arr = { 0, 1, 2, 3 };  // contains 0, 1, 2, 3, 0, 0, 0, ...
array2d<int, 6, 8> arr2;                  // contains garbage

Update 31/07/2016

Three years have passed quickly and compiler implementers improved standard compliance of their products up to the level where default constructor is not considered ambiguous in the presence of variadic constructor anymore. So, in practice, we don't need additional T t argument to variadic constructor to disambiguate constructors.

Both

array2d() {}

and

array2d() = default;

will leave array uninitialized if object is being constructed without arguments. This behavior is consistent on all major compilers. Full example (rextester):

#include <array>
#include <iostream>

template<class T, size_t rows, size_t cols>
class array2d
{
  public:
    std::array<T, rows * cols> m_Data;

    array2d() = default;

    template <typename... Types>
    array2d(Types... ts) : m_Data{ { ts... } } {}
};

int main()
{
    array2d<int, 6, 8> arr_init = { 0, 1, 2, 3 };
    array2d<int, 6, 8> arr_default;


    std::cout << "Initialized: \n";
    for(const auto& a : arr_init.m_Data)
        std::cout << a << " ";
    std::cout << "\n";

    std::cout << "Default: \n";
    for(const auto& a : arr_default.m_Data)    
        std::cout << a << " ";

    std::cout << "\n";
}

Output:

Initialized: 
0 1 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
Default: 
2 0 -519559849 32558 1 32558 0 0 -519634912 32558 -526739248 32558 1 0 2 0 6295032 0 -519531243 32558 0 0 -1716075168 32765 6295648 0 4196192 0 6295648 0 -526527271 32558 1 0 2 0 6295032 0 4196845 0 124 0 0 0 4196768 0 4196518 0 

Removing default constructor still leads to variadic constructor to be called and array being default-initialized (with all-zeros in our case).

Thanks @Alek for bumping this thread and for drawing attention to these facts, and also thanks to all people working hard on compiler development.

Solution 5

You can't, arrays are not like other types (and don't have constructors taking a std::initializer_list).

Try this instead:

struct Foo  
{  
  const std::vector<int>   data;
  Foo(std::initializer_list<int> ini) : data(ini)
  {}
}; 
Share:
43,232
swestrup
Author by

swestrup

Stirling G. Westrup (He/Him) became hooked on Science Fiction at the age of 9, when he was lent his brother's copy of The Spacehounds of IPC, by E.E. "Doc" Smith. Skipping over the mushy stuff at the beginning, he quickly found the bit where the alien ships start firing ray-beams, and he's never looked back. He determined then-and-there to become one of those adventuring scientists who build their own spaceships and ray guns and go out to confront the alien menace on its own turf. He maintained this determination until well into a physics major in university. However the realities of the current technology of warp drives and the lack of jobs for nuclear physicists caused him to shift to his second love: computers. For the last 20+ years since he left university, he's been an internet denizen and working programmer engaged in everything from educational software to embedded communications systems. A few years ago he was Director of Research and Development for a small Montreal-based computer-game company called Strategy First, which he helped to found, but is now engaged with another startup. In his spare time he reads science fiction and fantasy, and dabbles in writing and running role-playing game supplements and scenarios.

Updated on January 11, 2020

Comments

  • swestrup
    swestrup over 4 years

    I'm getting up to speed with C++0x, and testing things out with g++ 4.6

    I just tried the following code, thinking it would work, but it doesn't compile. I get the error:

    incompatible types in assignment of ‘std::initializer_list<const int>’ to ‘const int [2]’

    struct Foo
      {
        int const data[2];
    
        Foo(std::initializer_list<int const>& ini)
        : data(ini)
        {}
      };
    
    Foo f = {1,3};