Как я могу инициализировать массив членов с initializer_list?
Я набираюсь скорости с 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 ответов
Вы можете использовать конструктор шаблонов переменных вместо конструктора списка инициализаторов:
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());
}
}
Однако при этом вы теряете границы времени компиляции, которые предоставляет предыдущее решение.
Насколько я могу судить, использование инициализации списка аргумента функции конструктора (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() )
Просто небольшое дополнение к великолепному ответу JohannesD.
В случае отсутствия аргументов, переданных 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 за продвижение этой темы и за привлечение внимания к этим фактам, а также благодаря всем людям, усердно работающим над разработкой компилятора.
Согласно обсуждению здесь:
правильный синтаксис для второго решения Potatoswatter:
Foo f( {{1,3}} ); //two braces
немного некрасиво, не согласуется с обычным употреблением
Вы можете определить constexpr
функция, которая преобразует список инициализаторов в массив. Вы вызываете последнюю (третью) функцию. Другой рекурсивно создает пакет параметров шаблона из списка инициализаторов и создает массив после того, как будет прочитано достаточно много элементов списка.
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());
}
Вы не можете, массивы не похожи на другие типы (и не имеют конструкторов, принимающих std::initializer_list).
Попробуйте это вместо этого:
struct Foo
{
const std::vector<int> data;
Foo(std::initializer_list<int> ini) : data(ini)
{}
};
Пока это не работает
#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};
Если вам не нужна проверка границ, то сработает следующее.
struct Foo {
int const data[2];
Foo(std::initializer_list<int> ini)
: data{*std::begin(ini), *std::next(std::begin(ini), 1)} {}
};