Open kelbon opened 2 years ago
Есть библиотека, которую начинали стандартизировать https://github.com/lewissbaker/cppcoro
Есть несколько альтернативных библиотек, например https://github.com/David-Haim/concurrencpp
Если действительно готовы взяться за идею и довести её до конца, то стоит начать с изучения предложений от Lewiss Baker и описания того, как улучшить его наработки.
Есть библиотека, которую начинали стандартизировать https://github.com/lewissbaker/cppcoro
К сожалению она не выглядит как поддерживаемая на данный момент. Там очень много смелых идей, скажем fmap внутри генератора, но сейчас по сути std::views::transform, аналогично наработки cancellation token/ source, которые теперь std::stop_source/ std::stop_token, то есть многие вещи устарели Или скажем "концепт" awaiter из cppcoro
template<typename T>
struct is_awaiter<T, std::void_t<
decltype(std::declval<T>().await_ready()),
decltype(std::declval<T>().await_suspend(std::declval<std::experimental::coroutine_handle<>>())),
decltype(std::declval<T>().await_resume())>> :
std::conjunction<
std::is_constructible<bool, decltype(std::declval<T>().await_ready())>,
detail::is_valid_await_suspend_return_value<
decltype(std::declval<T>().await_suspend(std::declval<std::experimental::coroutine_handle<>>()))>>
{};
Просто нерабочий, т.к. фактически запрещает awaiter принимать в await_suspend любой тип кроме std::coroutine_handle\<void> (неявная конвертация есть только в одну сторону, плюс возможны ситуации где используются методы, которых нет в std::coroutine_handle\<void> или стоит requires, отсекающий \<void> специализацию и т.д.(эта же проблема есть в пропозале task от Гора Нишанова, не знаю исправлена ли она сейчас) ) cppcoro также практически не предоставляет никаких инструментов для написания своих корутин, даже внутри библиотеки каждый из типов корутин описан индивидуально с нуля, не поддержаны аллокаторы / memory resources. В целом cppcoro выглядит как набор смелых идей, но не проглядывается общей концепции и системного подхода, скажем async_generator\<T>, (пример из cppcoro):
cppcoro::async_generator<int> ticker(int count, threadpool& tp)
{
for (int i = 0; i < count; ++i)
{
co_await tp.delay(std::chrono::seconds(1));
co_yield i;
}
}
cppcoro::task<> consumer(threadpool& tp)
{
auto sequence = ticker(10, tp);
for co_await(std::uint32_t i : sequence)
{
std::cout << "Tick " << i << std::endl;
}
}
Лично я не вижу причин делать это вообще асинхронной операцией, если оба потока вынуждены друг друга ждать в итоге, а как использовать этот генератор иначе - непонятно. С другой стороны можно было бы представить библиотеку, которая даёт базу для создания прикладных корутин и я наследуясь от базовой корутины лишь доопределяю один await_transform для того чтобы действительно безопасно и удобно делать yield в нужный мне контейнер. И нет, с async_generator так не получится, т.к. тип корутины состоит из двух типов - промиса и типа-владельца, промолчу уж про возможную поломку внутренней логики при шадоувинге методов промиса. Придётся переписывать всё с нуля.
Также я смотрел предложения по std::generator, главные претензии к нему - неявные для пользователя крайне костыльные шаблонные аргументы, тайп ерейз для поддержки аллокаторов, требование именно аллокатора, хотя в данной ситуации требования явно должны быть меньше, проблемы с ссылками. Главное, что все эти проблемы выдуманные. Они все решаемы. Декларация моего генератора:
template <typename Yield, typename MemoryResource = std::allocator<std::byte>>
struct generator;
ясные для пользователя шаблонные аргументы, никакого тайп ерайза, 0 оверхеда на аллокации, если аллокатор stateless(то есть мы не платим за то, чего не используем), никаких проблем с ссылками, потому что значения не "выбрасываются" из генератора, а "показываются", то есть при co_yield Lvalue; потребителю будет показываться именно этот объект lvalue, а при co_yield Rvalue; создаваться объект и хранится до следующего пробуждения генератора, при этом on consumer side это всегда выглядит как взаимодействие с lvalue, у этого есть множество интересных применений, это снижает оверхед и вероятность вылетания исключения(для lvalue очевидно всегда noexcept), а также позволяет создавать генераторы ссылок без каких либо странных вещей и проблем с лайфтаймами. Можно сделать из генератора хранилище, почему нет?
generator<int> ViewGenerator() {
int x = 1;
int y = 2;
while (x < 100) {
co_yield ++x;
co_yield y;
}
}
Описание: Нам очевидно нужна поддержка корутин в стандарте. Минимальный набор готовых корутин, инфраструктура вокруг них(utility), концепты, набор "сделай сам" для кастомизации под конкретные нужды.
Примеры Смешной и неэффективный пример, потому что все итак знают, что генераторы полезны
Нужны корутины также и для асинхронного кода, конечно же
<Код c реализацией вашей идеи, если есть> Есть https://github.com/kelbon/UndefinedBehavior_GoldEdition