Инициализация списком объекта своего класса как в std::vector

Рейтинг: 1Ответов: 1Опубликовано: 27.08.2023

В std::vector есть возможность инициализации объекта с помощью {}, например:

std:vector<int> myvec = {1, 2, 3}

Существует ли возможность создать такой конструктор, с помощью которого можно будет подобным образом инициализировать объект своего класса?

Помимо агрегатной инициализации, так как очевидно, что для обеспечения работы такой инициализации необходимо было бы создать очень много переменных, что нерационально.

Ответы

▲ 3Принят

std::vector использует для этих целей конструктор с параметром типа std::initializer_list<тип_элемента>.

Раньше хорошим тоном считалось к нему в пару добавлять конструктор от двух итераторов. Но в прекрасном новом мире теперь в моде ranges, поэтому второй конструктор можно сделать примерно такой: (аргумент - любой тип с перегруженными .begin(), .end(), и т.п.)

template <std::ranges::input_range T>
requires( std::same_as<std::ranges::range_value_t<T>, тип_элемента>
    && !std::same_as<std::remove_cvref_t<T>, MyClass>
)
MyClass(T &&list) {}

(реализация у обоих конструкторов будет одинаковая, ее можно вынести в шаблонную функцию)


Есть еще вариативные шаблоны: template <typename ...P> MyClass(P &&... params) - такой конструктор может принять несколько объектов разных типов. Но использовать его для списка однотипных объектов выглядит расточительно (под разное количество элементов генерируются отдельные экземпляры).


Если конструктор/функцию не хочется шаблонировать (чтобы принимать произвольный range), то лучшим вариантом будет принимать std::span<const тип_элемента> - в него можно передать и список (в двойных скобках - {{1, 2, 3}}, так что перегрузка с initializer_list становится не очень нужна), и массив, и любой контейнер, элементы которого лежат непрерывно в памяти.


Если мы пишем функцию, а не конструктор, то перегрузки с range и с initializer_list можно объединить в одну, написав template </*...*/ T = std::initializer_list<тип_элемента>>. Почему-то с конструкторами фокус не проходит.


Если вы потом собираетесь полученный список класть в какой-то контейнер (например std::vector), то лучше этот же тип сделать параметром конструктора (по значению, потом применить std::move), и больше никакие конструкторы не писать.