How do I initialize a member array with an initializer_list?

C++ArraysC++11Initializer List

C++ Problem Overview


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

C++ Solutions


Solution 1 - C++

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 - C++

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 - C++

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 - C++

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 - C++

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)
  {}
}; 

Solution 6 - C++

You can define a constexpr function that converts an initializer list to an array. The last (third) function is the one you call. The other create recursively a template parameter pack from the initializer list, and create the array once sufficiently many list elements have been read.

template <typename T, size_t N, typename... Ts>
constexpr enable_if_t<(sizeof...(Ts) == N), array<T, N> >
array_from_initializer_list(const T *const beg, const T *const end,
                            const Ts... xs) {
  return array<T, N>{xs...};
}

template <typename T, size_t N, typename... Ts>
constexpr enable_if_t<(sizeof...(Ts) < N), array<T, N> >
array_from_initializer_list(const T *const beg, const T *const end,
                            const Ts... xs) {
  return array_from_initializer_list<T, N>(beg + 1, end, *beg, xs...);
}

template <typename T, size_t N>
constexpr array<T, N> array_from_initializer_list(initializer_list<T> l) {
  return array_from_initializer_list<T, N>(l.begin(), l.end());
}

Solution 7 - C++

While this does not work:

#include <initializer_list>
struct Foo
{
  const int data[2];

  constexpr Foo(const std::initializer_list<int>& ini): data{ini}  {}
};

Foo f = {1,3};

I found this simple approach to work nicely:

struct Foo
{
  const int data[2];

  constexpr Foo(const int a, const int b): data{a,b}  {}
};

Foo f = {1,3};

Of course the variadic template approach is probably better if you have a lot of elements, but in this simple case, this will probably suffice.

That is, if you want to explicitly define the constructor from initializer lists. For most POD cases this is fine and dandy:

struct Foo
{
  const int data[2];
};
Foo f = {1,3};

Solution 8 - C++

If you don't care about bounds checking, then the following will work.

struct Foo {
    int const data[2];
    Foo(std::initializer_list<int> ini)
        : data{*std::begin(ini), *std::next(std::begin(ini), 1)} {}
};

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionswestrupView Question on Stackoverflow
Solution 1 - C++JohannesDView Answer on Stackoverflow
Solution 2 - C++PotatoswatterView Answer on Stackoverflow
Solution 3 - C++haohaoleeView Answer on Stackoverflow
Solution 4 - C++Ivan Aksamentov - DropView Answer on Stackoverflow
Solution 5 - C++Bo PerssonView Answer on Stackoverflow
Solution 6 - C++Erik SchnetterView Answer on Stackoverflow
Solution 7 - C++franjesusView Answer on Stackoverflow
Solution 8 - C++Felix GlasView Answer on Stackoverflow