Как создать конструктор, позволяющий использовать braced-init-list?
У меня есть класс Phenotype
со следующим конструктором:
Phenotype(uint8 init[NUM_ITEMS]);
Я могу создать объект Phenotype
следующим образом:
uint8 data[] = {0, 0, 0, 0, 0};
Phenotype p(data);
Однако, когда я пытаюсь создать объект так:
Phenotype p = {0, 0, 0, 0, 0};
я получаю ошибку. Вывод:
$ make
g++ -Wall -g main.cpp -std=c++0x
main.cpp: In function ‘int main(int, char**)’:
main.cpp:109: error: no matching function for call to ‘Phenotype::Phenotype(<brace-enclosed initializer list>)’
main.cpp:37: note: candidates are: Phenotype::Phenotype(uint8*)
Ошибка, похоже, указывает на то, что существует способ определить конструктор, который принимает инициализатор с фигурными скобками. Кто-нибудь знает, как это можно реализовать?
2 ответ(ов)
Это можно сделать только для агрегатов (массивов и некоторых классов). Напротив распространенного мнения, это также работает для многих классов, не являющихся POD. Однако, написать конструктор, который принимает их, невозможно.
Поскольку вы отметили это как "C++0x", это теперь возможно. Магические слова — "конструктор с инициализатором списка". Пример выглядит следующим образом:
Phenotype(std::initializer_list<uint8_t> c) {
assert(c.size() <= std::size(m_array));
std::copy(c.begin(), c.end(), m_array);
}
// используется так
Phenotype p1{1, 2, 3};
Phenotype p2({1, 3, 2}); // тоже работает
Phenotype p3(1, 2, 3); // не работает
Однако такая инициализация будет по умолчанию создавать массив, а затем использовать оператор присваивания. Если ваша цель — скорость и безопасность (вы получаете ошибки компиляции при слишком большом количестве инициализаторов!), вы также можете использовать обычный конструктор с вариативным шаблоном.
Это может быть более общим, чем нужно (часто инициализатор списка вполне достаточен, особенно для простых целых чисел). Он использует "перфектное пересылание", так что аргумент rvalue может быть перемещен в элемент массива:
template<typename ...T>
Phenotype(T&&...t): m_array{ std::forward<T>(t)... } {
}
// используется так
Phenotype p1{1, 2, 3};
Phenotype p2(1, 2, 3); // тоже работает
Phenotype p3({1, 2, 3}); // не работает
Это сложный выбор!
Правка: В последнем примере всё же работает, поскольку конструктор не был объявлен explicit
, так что он может использовать конструктор копирования Phenotype
, создавая временный объект Phenotype
и копируя его в p3
. Но именно так мы и не хотели бы, чтобы эти вызовы выглядели 😃
Вам нужно использовать шаблонный тип std::initializer_list
. Вот пример:
#include <iostream>
class X {
public:
X(std::initializer_list<int> list) {
for (auto i = list.begin(); i != list.end(); i++) {
std::cout << *i << std::endl;
}
}
};
int main() {
X x = {1, 2, 3, 4, 5};
}
В этом коде класс X
имеет конструктор, который принимает std::initializer_list<int>
. Это позволяет вам инициализировать объект x
с помощью списка инициализации, который состоит из целых чисел. В конструкторе мы проходим по элементам списка и выводим их на экран.
Почему `std::initializer_list` не поддерживает оператор подиндексации?
Когда действительно стоит использовать noexcept?
Возможно ли вывести тип переменной в стандартном C++?
Можно ли вручную определить преобразование для класса enum?
Стоит ли игнорировать предупреждение "-Wmissing-braces" от gcc/clang?