Завершение через несколько вложенных сопрограмм - PullRequest
1 голос
/ 09 мая 2020

Я пытаюсь понять, как именно новые сопрограммы работают в C ++ 20, но кроме очень тривиальных примеров я не могу заставить их работать.

Моя цель - создать глубокие вложенные функции, которые позволяют можно сломать и вернуть управление большей части внешнего кода, а после некоторого условия он вернет управление этой внутренней функции. Это эффективно setjmp и longjmp.

Я испортил код, используя некоторые примеры, найденные в net:

#include <iostream>
#include <coroutine>
#include <optional>

template <typename T>
struct task 
{
    struct task_promise;

    using promise_type = task_promise;
    using handle_type = std::coroutine_handle<promise_type>;

    mutable handle_type m_handle;

    task(handle_type handle)
        : m_handle(handle) 
    {

    }

    task(task&& other) noexcept
        : m_handle(other.m_handle)
    {
        other.m_handle = nullptr;
    }

    bool await_ready()
    {
        return m_handle.done();
    }

    bool await_suspend(std::coroutine_handle<> handle)
    {
        if (!m_handle.done()) {
            m_handle.resume();
        }
        return !m_handle.done();
    }

    auto await_resume()
    {
        return result();
    }

    T result() const
    {     
        if (!m_handle.done())
            m_handle.resume();

        return *m_handle.promise().m_value;
    }

    //manualy wait for finish
    bool one_step()
    {
        if (!m_handle.done())
            m_handle.resume();
        return !m_handle.done();
    }

    ~task()
    {
        if (m_handle)
            m_handle.destroy();
    }

    struct task_promise 
    {
        std::optional<T>    m_value {};

        auto value()
        {
            return m_value;
        }

        auto initial_suspend()
        {
            return std::suspend_always{};
        }

        auto final_suspend()
        {
            return std::suspend_always{};
        }

        auto return_value(T t)
        {
            m_value = t;
            return std::suspend_always{};
        }

        task<T> get_return_object()
        {
            return {handle_type::from_promise(*this)};
        }

        void unhandled_exception()
        {
            std::terminate();
        }

        void rethrow_if_unhandled_exception()
        {

        }
    };

};

static task<int> suspend_one()
{
    std::cout<< "suspend_one in\n";
    co_await std::suspend_always();
    std::cout<< "suspend_one return\n";
    co_return 1;
}
static task<int> suspend_two()
{
    std::cout<< "suspend_two -> suspend_one #1\n";
    auto a = co_await suspend_one();
    std::cout<< "suspend_two -> suspend_one #2\n";
    auto b = co_await suspend_one();
    std::cout<< "suspend_two return\n";
    co_return a + b;
}

static task<int> suspend_five()
{
    std::cout<< "suspend_five -> suspend_two #1\n";
    auto a = co_await suspend_two();
    std::cout<< "suspend_five -> suspend_one #2\n";
    auto b = co_await suspend_one();
    std::cout<< "suspend_five -> suspend_two #3\n";
    auto c = co_await suspend_two();
    std::cout<< "suspend_five return\n";
    co_return a + b + c;
}

static task<int> run()
{
    std::cout<< "run -> suspend_two #1\n";
    auto a = co_await suspend_two();
    std::cout<< "run -> suspend_one #2\n";
    auto b = co_await suspend_one();
    std::cout<< "run -> suspend_five #3\n";
    auto c = co_await suspend_five();
    std::cout<< "run -> suspend_one #4\n";
    auto d = co_await suspend_one();
    std::cout<< "run -> suspend_two #5\n";
    auto e = co_await suspend_two();
    std::cout<< "run return\n";
    co_return a + b + c + d + e;
}

int main()
{
    std::cout<< "main in\n";
    auto r = run();
    std::cout<< "main -> while\n";
    while (r.one_step()){  std::cout<< "<<<< while loop\n"; }

    std::cout<< "main return\n";
    return r.result();
}

https://gcc.godbolt.org/z/JULJCi

Функция run работает должным образом, но у меня проблема, например, с suspend_five, где она никогда не достигает строки

std::cout<< "suspend_five -> suspend_two #3\n";

Вероятно, моя версия task полностью не работает, но я понятия не имею, где найдите эту ошибку или как она должна выглядеть. Или просто то, чего я хочу достичь, не поддерживается C ++ 20? co_yeld может быть кандидатом для обхода, потому что кажется более возможным вложить их вручную (for (auto z : f()) co_yeld z;), но цель этого вопроса - понять внутренний механизм c функциональности C ++ 20, который решает некоторые существующие проблемы.

1 Ответ

0 голосов
/ 11 мая 2020

После некоторого покопания и чтения документации я пришел к выводу, что код вида:


    //manualy wait for finish
    bool one_step()
    {
        if (!m_handle.done())
            m_handle.resume();
        return !m_handle.done();
    }

полностью сломан и выполнен в обратном направлении. Вы не можете m_handle.resume(), если внутренняя задача не завершена, например, m_handle.inner.done(). Это означает, что нам нужно сначала «продвинуть» вперед большую часть внутренней задачи, прежде чем мы сможем переместить внешнюю. ожидайте.

#include <cstdio>
#include <coroutine>
#include <optional>

namespace
{

template <typename T>
struct task 
{
    struct task_promise;

    using promise_type = task_promise;
    using handle_type = std::coroutine_handle<promise_type>;

    mutable handle_type m_handle;

    task(handle_type handle)
        : m_handle(handle) 
    {

    }

    task(task&& other) noexcept
        : m_handle(other.m_handle)
    {
        other.m_handle = nullptr;
    }

    bool await_ready()
    {
        return false;
    }

    bool await_suspend(std::coroutine_handle<> handle)
    {
        return true;
    }

    bool await_suspend(std::coroutine_handle<promise_type> handle)
    {
        handle.promise().m_inner_handler = m_handle;
        m_handle.promise().m_outer_handler = handle;
        return true;
    }

    auto await_resume()
    {
        return *m_handle.promise().m_value;
    }

    //manualy wait for finish
    bool one_step()
    {
        auto curr = m_handle;
        while (curr)
        {
            if (!curr.promise().m_inner_handler)
            {
                while (!curr.done())
                {
                    curr.resume();
                    if (!curr.done())
                    {
                        return true;
                    }
                    if (curr.promise().m_outer_handler)
                    {
                        curr = curr.promise().m_outer_handler;
                        curr.promise().m_inner_handler = nullptr;
                    }
                    else
                    {
                        return false;
                    }
                }
                break;
            }
            curr = curr.promise().m_inner_handler;
        }
        return !curr.done();
    }

    ~task()
    {
        if (m_handle)
            m_handle.destroy();
    }

    struct task_promise 
    {
        std::optional<T>    m_value {};
        std::coroutine_handle<promise_type> m_inner_handler {};
        std::coroutine_handle<promise_type> m_outer_handler {};

        auto value()
        {
            return m_value;
        }

        auto initial_suspend()
        {
            return std::suspend_never{};
        }

        auto final_suspend()
        {
            return std::suspend_always{};
        }

        auto return_value(T t)
        {
            m_value = t;
            return std::suspend_always{};
        }

        task<T> get_return_object()
        {
            return {handle_type::from_promise(*this)};
        }

        void unhandled_exception()
        {
            std::terminate();
        }

        void rethrow_if_unhandled_exception()
        {

        }
    };

};

task<int> suspend_one()
{
    std::printf("suspend_one \\\n");
    co_await std::suspend_always();
    std::printf("suspend_one /\n");
    co_return 1;
}
task<int> suspend_two()
{
    auto a = co_await suspend_one();
    auto b = co_await suspend_one();
    co_return a + b;
}

task<int> suspend_five()
{
    auto a = co_await suspend_two();
    auto b = co_await suspend_two();
    co_return 1 + a + b;
}

task<int> run()
{
    std::printf("run\n");
    auto a = co_await suspend_five();
    auto b = co_await suspend_five();
    auto c = co_await suspend_five();
    co_return 5 + a + b + c;
}

}

int main()
{
    std::printf( "main in\n");
    auto r = run();
    std::printf( "main -> while\n");
    while (r.one_step()){  std::printf("              while loop\n"); }

    std::printf( "main return\n");
    return r.await_resume();
}

https://gcc.godbolt.org/z/EUdTwu

Самое главное:

    bool await_suspend(std::coroutine_handle<promise_type> handle)
    {
        handle.promise().m_inner_handler = m_handle;
        m_handle.promise().m_outer_handler = handle;
        return true;
    }

Где я связываю каждый кадр вместе и позволяю нам sh самая внутренняя, прежде чем мы смогли go "up stack".

Думаю, это можно рассматривать как полную сопрограмму для бедняков.

...