Как я могу инициализировать массив членов с initializer_list? - PullRequest
47 голосов
/ 05 апреля 2011

Я набираю скорость с C ++ 0x и тестирую с помощью g ++ 4.6

Я только что попробовал следующий код, думая, что он будет работать, но он не компилируется.Я получаю ошибку:

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

Ответы [ 7 ]

54 голосов
/ 05 апреля 2011

Вместо конструктора списка инициализатора можно использовать конструктор шаблонов с переменным числом аргументов:

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
}

РЕДАКТИРОВАТЬ: Если вы можете жить без константности, другим способом будет пропустить инициализацию и заполнить массив в теле функции:

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

Тем не менее, таким образом вы теряете границы времени компиляции, проверяя, что обеспечивает предыдущее решение.

17 голосов
/ 05 апреля 2011

Насколько я могу судить, использование инициализации списка аргумента функции конструктора (8.5.4 / 1) должно быть законным и решает многие из проблем, описанных выше. Однако GCC 4.5.1 на ideone.com не может найти конструктор и отклоняет его.

#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

Если вы действительно настаиваете на initializer_list, вы можете использовать reinterpret_cast, чтобы превратить базовый массив initializer_list в массив в стиле C.

Foo(std::initializer_list<int> ini) // pass without reference- or cv-qualification
: data( reinterpret_cast< std::array< int, 2 > const & >( * ini.begin() )
5 голосов
/ 20 августа 2013

Просто небольшое дополнение к великолепному JohannesD answer .

Если аргумент foo не передан в аргументы, массив будет инициализирован по умолчанию. Но иногда вы хотите сохранить основной массив неинициализированным (возможно, из-за соображений производительности). Вы не можете добавить конструктор по умолчанию вместе с конструктором с переменными шаблонами. Обходной путь является дополнительным аргументом для конструктора с переменными шаблонами, чтобы отличить его от конструктора с нулевым аргументом:

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

Итак, теперь вы можете включить-инициализировать объект или оставить его неинициализированным:

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

Обновление 31/07/2016

Прошло три года быстро, и разработчики компиляторов улучшили соответствие своих продуктов стандартам до уровня, когда конструктор по умолчанию больше не считается неоднозначным при наличии конструктора с переменными числами. Таким образом, на практике нам не нужен дополнительный аргумент T t к конструктору с переменными значениями для устранения неоднозначности конструкторов.

Оба

array2d() {}

и

array2d() = default;

оставит массив неинициализированным, если объект создается без аргументов. Такое поведение одинаково для всех основных компиляторов. Полный пример ( 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";
}

Выход:

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 

Удаление конструктора по умолчанию по-прежнему приводит к вызову конструктора с переменными параметрами и инициализации массива по умолчанию (со всеми нулями в нашем случае).

Спасибо @Alek за продвижение этой темы и за внимание к этим фактам, а также благодаря всем людям, усердно работающим над разработкой компилятора.

5 голосов
/ 11 июня 2011

Согласно обсуждению здесь :

правильный синтаксис для второго решения Potatoswatter:

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

немного некрасиво, не согласуется с обычным употреблением

1 голос
/ 05 апреля 2011

Вы не можете, массивы не похожи на другие типы (и не имеют конструкторов, принимающих std :: initializer_list).

Попробуйте вместо этого:

struct Foo  
{  
  const std::vector<int>   data;
  Foo(std::initializer_list<int> ini) : data(ini)
  {}
}; 
0 голосов
/ 31 января 2018

Если вам не нужна проверка границ, сработает следующее.

struct Foo {
    int const data[2];
    Foo(std::initializer_list<int> ini)
        : data{*std::begin(ini), *std::next(std::begin(ini), 1)} {}
};
0 голосов
/ 31 января 2018

Пока это не работает:

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

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

Foo f = {1,3};

Я нашел этот простой подход для хорошей работы:

struct Foo
{
  const int data[2];

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

Foo f = {1,3};

Конечно, вариационный шаблонный подход, вероятно, лучше, если у вас много элементов, но в этом простом случае этого, вероятно, будет достаточно.

То есть, если вы хотите явно определить конструктор из списков инициализатора. Для большинства случаев POD это хорошо и модно:

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