Сопрограммы C ++ 20: реализация ожидаемого будущего - PullRequest
9 голосов
/ 10 марта 2019

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

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

Screengrab at timestamp 37:41 of James McNellis talk at 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.Возможно, они не идеальны, но cppreference явно заявляет valid() == false в качестве постусловия вызова then, поэтому я считаю правильным выйти из первоначального будущего.

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

1 Ответ

2 голосов
/ 06 апреля 2019

Как вы упомянули сами, проблема в том, что 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());
    }
...