Сопрограммы C++20: реализация ожидаемого будущего

Поскольку Coroutines TS был принят в C++20 на совещании ISO в Kona, я начал немного подыгрывать им. Clang уже имеет достойную поддержку сопрограмм, но реализация поддержки библиотеки все еще отсутствует. В частности, ожидаемые типы, такие как std::future, std::generator и т. д. еще не были реализованы.

Таким образом, я взял на себя, чтобы сделать std::future awaitable. Я в значительной степени следил за выступлением Джеймса Макнеллиса на CppCon 2016, в частности за этим слайдом:

Снимок экрана с отметкой времени 37:41 о выступлении Джеймса МакНеллиса на 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());
    }
Другие вопросы по тегам