Использование C++20 Coroutines для генерации интервалов
Попробовал использовать C++20 coroutines для генерации интервалов. Выглядит всё довольно неплохо. Вот небольшой пример использования.
Пусть у нас есть класс, содержащий внутри себя вектор уникальных указателей на векторы целых чисел, и некоторые из этих указателей могут быть null
. Надо вернуть из функции класса интервал, содержащий все элементы ненулевых векторов и какую-нибудь заданную последовательность (например 10 единиц) для нулевых векторов:
class my_class {
public:
auto data() const {
// ???
}
private:
std::vector<std::unique_ptr<std::vector<int>>> data_;
};
С помощью одной лишь библиотеки ranges
эта проблема не имеет простого решения. У нас есть адаптер std::ranges::views::join
, который объединяет интервал интервалов в один интервал, но проблема в том, что интервалы в объединяемой последовательности должны иметь один и тот же тип, а у нас тип выбирается во время выполнения.
static std::array<int, 10> once_array;
auto data() const {
auto fn = [](auto && vptr) {
if (vptr) {
// return type is std::ranges::ref_view<std::vector<int>>
return (*vptr.get()) | std::ranges::views::all;
} else {
// return type is std::ranges::ref_view<std::array<int, 10>>
return ones_array | std::ranges::views::all;
}
};
auto data_ranges = data_ | std::ranges::views::transform(fn);
return data_ranges | std::ranges::views::join;
}
Здесь помог бы какой-нибудь адаптер std::ranges::views::conditional
, который во время выполнения выбирает один из двух интервалов, возвращаемых функторами, но такого адаптера нету. Поэтому придётся вручную реализовывать собственный итератор, который будет ходить по вектору указателей.
С помощью корутин эта проблема решается гораздо проще. В стандарте C++20 определены только ключевые слова для поддержки корутин и описаны концепции классов, которые эти корутины должны возвращать, но самих классов в стандартной библиотеке нету (их планируют добавить в стандарт C++23). Поэтому нужно взять одну из библиотек, имеющих классы для генерации интервалов, например ranges-v3, которая имеет шаблонный класс ranges::experimental::generator
.
Функция data
реализуется следующим образом:
ranges::experimental::generator<int> data() const {
for (auto && vptr : data_) {
if (vptr) {
for (int i : *vptr) {
co_yield i;
}
} else {
for (int i = 0; i < 10; ++i) {
co_yield 1;
}
}
}
}
Всё просто и понятно, никаких адских последовательностей адаптеров и трансформеров, и не надо писать свой собственный итератор.
С помощью корутин можно генерировать и модифицируемые последовательности. Для этого корутина должна возвращать ranges::experimental::generator<int &>
. Но для нашего примера это неактуально, потому что непонятно что возвращать в случае нулевого вектора.
P. S. На самом деле у использования корутин для генерации интервалов есть один недостаток. Корутина всегда возвращает интервал, соответствующий концептам std::ranges::input_range
или std::ranges::output_range
, которые соответствуют обобщенному генератору последовательности. Интервалы C++20 гораздо мощнее и позволяют их использовать со сложными алгоритмами и генерировать более эффективный код.