How do I initialize a member array with an initializer_list?

匿名 (未验证) 提交于 2019-12-03 02:08:02

问题:

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

回答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.



回答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() ) 


回答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



回答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.



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


回答6:

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


回答7:

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


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!