Home C++ Introduction Decisions Loops Input/Output Functions Stack and Heap References Arrays Searching and Sorting Recursion Pointers Character and Strings Structures Classes Inheritance Exceptions Templatess STL Modern C++ Misc Books ----

C++ 20 New Features


Contents

Coroutines

co_yield

A coroutine is a function that can suspend and save it's state. It can be resumed later on. It achieves concurrency like behavior without the use of threads. Using it is somewhat involved and needs a study of the framework.


File: gen1.cpp
#include <coroutine>
#include <iostream>
#include <optional>
#include <utility>


using namespace std ;

// 1. The caller-facing type (e.g., 'Generator<int>').
template <typename T>
class Generator
{
    public:
    // 2. The compiler-internal type.
    class promise_type
    {
        public:
        T current_value;
        coroutine_handle<> caller_handle;

        Generator get_return_object()
        {
            // Give the Generator object a handle to its own coroutine frame.
            cout << "promise_type::get_return_object()" << endl ;
            return Generator{coroutine_handle<promise_type>::from_promise(*this)};
        }

        // This is called by the compiler for 'co_yield value;'.
        suspend_always yield_value(T value)
        {
            cout << "promise_type::yield_value()" << endl ;
            current_value = move(value);
            return {} ; // Always suspend here, returning control to the caller.
        }

        suspend_always initial_suspend()
        {
            cout << "promise_type::initial_suspend():" << endl ;
            return {};
        } // Suspend at the start.

        suspend_always final_suspend() noexcept
        { //when it is about to terminate
            cout << "promise_type::final_suspend():" << endl ;
            return {};
        } // Suspend at the end.
        void unhandled_exception() { throw; }
        void return_void() {} // Required for generators that don't co_return a final value.
    }; //promis_type

    coroutine_handle<promise_type> handle;

    // Public API for the caller to interact with the coroutine.
    optional<T> next()
    {
        if (!handle || handle.done())
        {
            return nullopt;
        }
        // Resume execution inside the coroutine until the next co_yield or end.
        handle.resume();
        if (handle.done()) {
            return nullopt;
        }
        return handle.promise().current_value;
    }

    // Constructor and cleanup.
    Generator() = default;
    explicit Generator(coroutine_handle<promise_type> h) : handle(h)
    {
        cout << "Generator: constructor." << endl ;
    }
    ~Generator() { if (handle) handle.destroy(); }
    Generator(const Generator&) = delete;
    Generator& operator=(const Generator&) = delete;
    Generator(Generator&& other) noexcept : handle(exchange(other.handle, nullptr)) {}
    Generator& operator=(Generator&& other) noexcept {
        if (this != &other) {
            if (handle) handle.destroy();
            handle = exchange(other.handle, nullptr);
        }
        return *this;
    }
};

// 3. The coroutine function itself.
Generator<int> count_up_to( int limit )
{
    for (int i1 = 0; i1 < limit; ++i1 )
    {
        co_yield i1 ;
        // This is where execution suspends and a value is returned.
    }
    // Execution ends here (final_suspend() is called).
}

// 4. Main function to use the generator.
int main()
{
    // Calling the function creates the coroutine but suspends immediately.
    Generator<int> my_counter = count_up_to( 5 ) ;
    cout << "main():Before my_counter.next()" << endl ;

    while (optional<int> value = my_counter.next() )
    {
        cout << "Generated value: " << *value << endl;
    }

    cout << "Coroutine finished." << endl;

    return 0;
}


If a function contains "co_await", "co_yield" or "co_return" then it is a
coroutine.



co_await

The "co_await" expects a certain object that has the
following methods:

1) await_ready():

Purpose: Checks if the asynchronous operation is already complete
(synchronous path).
Returns: true if ready to resume immediately, false
if suspension is needed.


2) await_suspend(std::coroutine_handle<> handle):
Purpose: Called when await_ready() returns false
(suspension occurs). It's where you schedule the
continuation.
Arguments: Receives the handle of the currently suspended coroutine.
Actions: Typically stores the handle, schedules
the coroutine to be resumed later (e.g., on an event loop/thread pool),
and returns void, bool, or std::coroutine_handle<>.

3) await_resume():
Purpose: Called after the coroutine resumes
(after await_suspend completes).
Returns: The value that the co_await expression yields
(e.g., the result of the async operation).