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

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

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

Снимок экрана  на отметке 37:41 выступления Джеймса Макнеллиса на CppCon 2016

В 2019 году у меня действительно возникли проблемы с (предположительно непроверенным?) Кодом на этом слайде:

  • Мне кажется, перегрузка operator co_await уже не актуальна? Вместо этого следует использовать необязательный await_transform из promise_type. Хотя не уверен, что правильно понял.
  • Продолжение then future захватывает дескриптор по значению, но функция-член resume не квалифицируется как const. Я обошел это, сделав лямбда mutable.

Кроме того, then и is_ready недоступны в std::future, но являются частью std::experimental::future, который является все еще отсутствует в моей версии libc ++. Чтобы избежать работы с Awaiter и реализовать будущие продолжения, я написал производный класс future, который является Awaitable и Awaiter. Насколько я понимаю, в конечном итоге и то, и другое будет верным и для std::future. Вы можете увидеть мой пример в Compiler Explorer. Он компилируется.

Однако он также имеет segfault. Это происходит в await_resume, когда вызывается get(). На самом деле это не удивительно, поскольку valid() в этот момент возвращает false (вызывая get() UB). Я думаю, это связано с тем, что когда then используется для продолжения будущего, исходный объект future перемещается в асинхронное будущее, тем самым делая недействительным старое будущее (*this во время вызова await_resume, то есть после перемещения). Моя реализация then во многом вдохновлена ​​этим ответом и этот код, который я нашел на GitHub. Возможно, они не идеальны, но cppreference явно указывает valid() == false как постусловие для звонит then, поэтому я считаю правильным выйти из первоначального будущего.

Что мне здесь не хватает? Эта «ошибка», кажется, уже присутствует на слайде выше. Как я могу решить эту проблему? Кто-нибудь знает (работающую) существующую реализацию Awaitable будущего? Спасибо.


person Jonas Greitemann    schedule 09.03.2019    source источник
comment
Поскольку Coroutines TS был принят в C ++ 20 на встрече ISO в Kona, следует отметить, что Coroutines TS - это не совсем то, что было принято в C ++ 20. Функция сопрограмм C ++ 20 похожа на Coroutines TS, но не идентична 1: 1.   -  person Nicol Bolas    schedule 10.03.2019
comment
Кто-нибудь знает о (работающей) существующей реализации Awaitable будущего? github.com/lewissbaker/cppcoro. Обратите внимание, что у автора также есть блог с пояснениями, например. lewissbaker.github.io/2017/11/17/   -  person sudo rm -rf slash    schedule 07.04.2019


Ответы (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());
    }
person Yehezkel B.    schedule 06.04.2019