Сопрограммы C++20: реализация ожидаемого будущего
Поскольку Coroutines TS был принят в C++20 на совещании ISO в Kona, я начал немного подыгрывать им. Clang уже имеет достойную поддержку сопрограмм, но реализация поддержки библиотеки все еще отсутствует. В частности, ожидаемые типы, такие как std::future
, std::generator
и т. д. еще не были реализованы.
Таким образом, я взял на себя, чтобы сделать std::future
awaitable. Я в значительной степени следил за выступлением Джеймса Макнеллиса на CppCon 2016, в частности за этим слайдом:
В 2019 году у меня возникли проблемы с (предположительно непроверенным?) Кодом на этом слайде:
- Мне кажется перегрузка
operator co_await
больше не вещь? Вместо этого следует использовать дополнительныйawait_transform
изpromise_type
, Не уверен, что я все понял правильно. -
then
продолжение будущего захватывает ручку по значению, ноresume
функция-член не является константной. Я работал над этим, делая лямбдуmutable
,
Также, then
а также is_ready
недоступны в std::future
но являются частью std::experimental::future
который все еще отсутствует в моей версии libC++. Чтобы избежать работы с Awaiter и реализовать будущие продолжения, я написал производный будущий класс, который является Awaitable и Awaiter. Насколько я понимаю, что в конечном итоге оба были бы верны std::future
, Вы можете увидеть мой пример в Compiler Explorer. Это компилируется.
Тем не менее, это также делает Segfault. Это происходит в await_resume
когда get()
называется. Это на самом деле не удивительно, так как valid()
возвращается false
в этот момент (позвонив get()
UB). Я думаю, это потому, что когда then
используется для продолжения будущего, исходный объект будущего перемещается в асинхронное будущее, что делает недействительным старое будущее (*this
в это время await_resume
называется, так после переезда). Моя реализация then
слабо вдохновлен этим ответом и этим кодом, который я нашел на GitHub. Они не могут быть идеальными, но в явной форме говорится valid() == false
в качестве постусловия вызова then
, поэтому я считаю правильным выйти из первоначального будущего.
Что мне здесь не хватает? Эта "ошибка", кажется, присутствует уже на слайде выше. Как я могу решить эту проблему? Кто-нибудь знает о (работающей) существующей реализации ожидаемого будущего? Благодарю.
1 ответ
Как вы упомянули сами, проблема в том, что future
переехал из после звонка .then()
, Хитрость заключается в том, чтобы переместить его обратно, когда он будет готов. Это можно сделать, если продолжение передано .then()
принимает future
, а не значение, которое оно имеет.
Вот функции, которые я взял из вашего кода и изменил. Я также изменил их от передачи вещей в std::async
в качестве параметров просто захватить их, поскольку это выглядит более интуитивно для меня, но это не важное изменение здесь.
template <typename Work>
auto then(Work&& w) -> co_future<decltype(w())> {
return { std::async([fut = std::move(*this), w = std::forward<Work>(w)]() mutable {
fut.wait();
return w();
})};
}
template <typename Work>
auto then(Work&& w) -> co_future<decltype(w(std::move(*this)))> {
return { std::async([fut = std::move(*this), w = std::forward<Work>(w)]() mutable {
return w(std::move(fut));
})};
}
void await_suspend(std::experimental::coroutine_handle<> ch) {
then([ch, this](auto fut) mutable {
*this = std::move(fut);
ch.resume();
});
}
Кстати, VS2017 жалуется на наличие обоих set_exception()
а также unhandled_exception()
в обещании типа. Я удалил set_exception()
и изменился unhandled_exception()
к этому:
void unhandled_exception() {
_promise.set_exception(std::current_exception());
}