60
4

8 回答 8

64

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.

于 2011-04-05T09:39:05.560 回答
19

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() )
于 2011-04-05T10:38:25.600 回答
6

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

于 2011-06-11T18:19:10.577 回答
6

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.

于 2013-08-20T19:09:35.997 回答
2

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)
  {}
}; 
于 2011-04-05T09:13:07.183 回答
1

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());
}
于 2020-01-10T19:18:29.770 回答
0

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};
于 2018-01-31T15:58:27.703 回答
0

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)} {}
};
于 2018-01-31T16:46:48.677 回答