Search code examples
c++c++20c++-coroutine

how to execute coroutine in an infinite loop, equivalent to looping inside coroutine?


Let's assume I have a coroutine function with a single co_await operator in the body waiting on an event, like this:

auto coro = [&]()->coroutine {
    co_await evt;
    std::cerr << "event was triggered\n";
};

I need to write a function void forever(auto&&) which takes a coroutine (forever(coro);) and executes it in an infinite loop, having the same effect as a coroutine with an infinite loop inside the body, i.e.

auto forever_coro = [&]->coroutine {
    for(auto counter = 1;;++counter) {
        co_await evt;
        std::cerr << "event was triggered " << counter << " time(s)\n";
    }
};

Update: forever() obviously has to be a coroutine itself, which must somehow be resumed when the callee is done. So the awaiter for that should probably be resumed when callee.done() returns true, but how that would trigger the resumption? The forever() function would look something like this:

auto forever(auto&& coro)->coroutine { 
    for(;;) co_await callee_awaiter(coro);
}

So basically the question becomes, how that callee_awaiter can be implemented?

Here is a demo


Solution

  • I've adapted your sample into an incomplete but working implementation of awaitable coroutine:

    struct coroutine;
    struct promise
    {
        struct {
            bool done{false};
            std::coroutine_handle<void> suspended_awaiter{};
    
            bool await_ready() noexcept {
                // We always suspend in the final awaiter to avoid the promise being destructed
                return false;
            }
    
            std::coroutine_handle<void> await_suspend(std::coroutine_handle<void>) noexcept {
                return suspended_awaiter ? std::move(suspended_awaiter) : std::noop_coroutine();
            }
    
            void await_resume() noexcept {}
        } final_awaiter;
    
        coroutine get_return_object();
        std::suspend_never initial_suspend() noexcept { return {}; }
    
        // return a final awaiter that's responsible for invoking continuation coroutines
        auto& final_suspend() noexcept { return final_awaiter; }
    
        void return_void() {}
        void unhandled_exception() {}
    };
    
    struct coroutine
    {
        using promise_type = ::promise;
    
        promise_type& promise;
    
        bool await_ready() noexcept {
            return promise.final_awaiter.done;
        }
        void await_suspend(std::coroutine_handle<promise_type> suspended) {
            promise.final_awaiter.suspended_awaiter = std::move(suspended);
        }
        void await_resume() {}
    };
    
    coroutine promise::get_return_object() { return {*this}; }
    
    coroutine forever(auto&& crtn)
    {
        while (true) {
            co_await crtn();
        }
    }
    

    Godbolt here.

    The incomplete part is that I've taken a shortcut to manage the promise lifetime. Namely: the coroutine relies on it's promise reference to stay valid even after completion. The way I guarantee that is by always suspending in final_suspend. This means that the coroutine_handle::destroy() must now be explicitly called to free the coroutine state. I have left that out of the code. If coroutine is neither movable nor copyable, you can just put the destroy call into ~coroutine. Otherwise you'll have to work out the lifetime you want yourself. If you end up needing help with that, then IMO that's a separate question.

    Also, I've completely ignored thread safety for now, because you didn't mention anything about it.