What are the mechanics of coroutines in C++20?

N4775 outlines the proposal for coroutines for C++20. It introduces an number of different ideas. The following is from my blog at https://dwcomputersolutions.net . More info can be found in my other posts.

Before we examine our whole Hello World coroutine program, go through the
various parts step-by-step. These include:

  1. The coroutine Promise
  2. The coroutine Context
  3. The coroutine Future
  4. The coroutine Handle
  5. The coroutine itself
  6. The subroutine that actually uses the coroutine

The entire file is included at the end of this
post.

The Coroutine

Future f()
{
    co_return 42;
}

We instantiate our coroutine with

    Future myFuture = f();

This is a simple coroutine that just returns the value 42. It is a coroutine
because it includes the keyword co_return. Any function that has the keywords
co_await, co_return or co_yield is a coroutine.

The first thing you will notice is that although we are returning an integer,
the coroutine return type is (a user defined) type Future. The reason is that
when we call our coroutine, we don’t run the function right now, rather we
initialize an object which will eventually get us the value we are looking for
AKA our Future.

Finding the Promised Type

When we instantiate our coroutine, the first thing the compiler does is find the
promise type that represents this particular type of coroutine.

We tell the compiler what promise type belongs to what coroutine function
signature by creating a template partial specialization for

template <typename R, typename P...>
struct coroutine_trait
{};

with a member called promise_type that defines our Promise Type

For our example we might want to use something like:

template<>
struct std::experimental::coroutines_v1::coroutine_traits<Future> {
    using promise_type = Promise;
};

Here we create a specialization of coroutine_trait specifies no parameters and
a return type Future, this exactly matches our coroutine function signature of
Future f(void). promise_type is then the promise type which in our case is
the struct Promise.

Now are a user, we normally will not create our own coroutine_trait
specialization since the coroutine library provides a nice simple way to
specify the promise_type in the Future class itself. More on that later.

The Coroutine Context

As mentioned in my previous post, because coroutines are suspend-able and
resume-able, local variables cannot always be stored in the stack. To store
non-stack-safe local variables, the compiler will allocate a Context object on
the heap. An instance of our Promise will be stored as well.

The Promise, the Future and the Handle

Coroutines are mostly useless unless they are able to communicate with the
outside world. Our promise tells us how the coroutine should behave while our
future object allow other code to interact with the coroutine. The Promise and
Future then communicate with each-other via our coroutine handle.

The Promise

A simple coroutine promise looks something like:

struct Promise 
{
    Promise() : val (-1), done (false) {}
    std::experimental::coroutines_v1::suspend_never initial_suspend() { return {}; }
    std::experimental::coroutines_v1::suspend_always final_suspend() {
        this->done = true;
        return {}; 
    }
    Future get_return_object();
    void unhandled_exception() { abort(); }
    void return_value(int val) {
        this->val = val;
    }
    
    int val;
    bool done;    
};

Future Promise::get_return_object()
{
    return Future { Handle::from_promise(*this) };
}

As mentioned, the promise is allocate when the coroutine is instantiated and
exits for the entire lifetime of the coroutine.

Once done, the compiler calls get_return_object This user defined function is
then responsible for creating the Future object and returning it to the
coroutine instatiator.

In our instance, we want our Future to be able to communicate with our coroutine
so we create our Future with the handle for our coroutine. This will allow our
Future to access our Promise.

Once our coroutine is created, we need to know whether we want to start running
it immediately or whether we want it to remain suspended immediately. This is
done by calling the Promise::initial_suspend() function. This function returns
an Awaiter which we will look in another post.

In our case since we do want the function to start immediately, we call
suspend_never. If we suspended the function, we would need to start the
coroutine by calling the resume method on the handle.

We need to know what to do when the co_return operator is called in
the coroutine. This is done via the return_value function. In this case we
store the value in the Promise for later retrieval via the Future.

In the event of an exception we need to know what to do. This is done by the
unhandled_exception function. Since in our example, exceptions should not
occur, we just abort.

Finally, we need to know what to do before we destroy our coroutine. This is
done via the final_suspend function In this case, since we want to retrieve
the result so we return suspend_always. The coroutine must then be destroyed
via the coroutine handle destroy method. Otherwise, if we return
suspend_never the coroutine destroys itself as soon as it finishes running.

The Handle

The handle give access to the coroutine as well as its promise. There are two
flavours, the void handle when we do not need to access the promise and the
coroutine handle with the promise type for when we need to access the promise.

template <typename _Promise = void>
class coroutine_handle;

template <>
class coroutine_handle<void> {
public:
    void operator()() { resume(); }
    //resumes a suspended coroutine
    void resume();
    //destroys a suspended coroutine
    void destroy();
    //determines whether the coroutine is finished
    bool done() const;
};

template <Promise>
class coroutine_handle : public coroutine_handle<void>
{
    //gets the promise from the handle
    Promise& promise() const;
    //gets the handle from the promise
    static coroutine_handle from_promise(Promise& promise) no_except;
};

The Future

The future looks like this:

class [[nodiscard]] Future
{
public:
    explicit Future(Handle handle)
        : m_handle (handle) 
    {}
    ~Future() {
        if (m_handle) {
            m_handle.destroy();
        }
    }
    using promise_type = Promise;
    int operator()();
private:
    Handle m_handle;    
};

int Future::operator()()
{
    if (m_handle && m_handle.promise().done) {
        return m_handle.promise().val;
    } else {
        return -1;
    }
}

The Future object is responsible for abstracting the coroutine to the outside
world. We have a constructor that takes the handle from the promise as per the
promise’s get_return_object implementation.

The destructor destroys the coroutine since in our case it is the future that
control’s the promise’s lifetime.

lastly we have the line:

using promise_type = Promise;

The C++ library saves us from implementing our own coroutine_trait as we did
above if we define our promise_type in the return class of the coroutine.

And there we have it. Our very first simple coroutine.

Full Source



#include <experimental/coroutine>
#include <iostream>

struct Promise;
class Future;

using Handle = std::experimental::coroutines_v1::coroutine_handle<Promise>;

struct Promise 
{
    Promise() : val (-1), done (false) {}
    std::experimental::coroutines_v1::suspend_never initial_suspend() { return {}; }
    std::experimental::coroutines_v1::suspend_always final_suspend() {
        this->done = true;
        return {}; 
    }
    Future get_return_object();
    void unhandled_exception() { abort(); }
    void return_value(int val) {
        this->val = val;
    }
    
    int val;
    bool done;    
};

class [[nodiscard]] Future
{
public:
    explicit Future(Handle handle)
        : m_handle (handle) 
    {}
    ~Future() {
        if (m_handle) {
            m_handle.destroy();
        }
    }
    using promise_type = Promise;
    int operator()();
private:
    Handle m_handle;    
};

Future Promise::get_return_object()
{
    return Future { Handle::from_promise(*this) };
}


int Future::operator()()
{
    if (m_handle && m_handle.promise().done) {
        return m_handle.promise().val;
    } else {
        return -1;
    }
}

//The Co-routine
Future f()
{
    co_return 42;
}

int main()
{
    Future myFuture = f();
    std::cout << "The value of myFuture is " << myFuture() << std::endl;
    return 0;
}

##Awaiters

The co_await operator allows us to suspend our coroutine and return control
back to the coroutine caller. This allows us to do other work while waiting our operation completes. When they do complete, we can resume them from
exactly where we left off.

There are several ways that the co_await operator will process the expression
on its right. For now, we will consider the simplest case and that is where our
co_await expression returns an Awaiter.

An Awaiter is a simple struct or class that implements the following
methods: await_ready, await_suspend and await_resume.

bool await_ready() const {...} simply returns whether we are ready to resume our
coroutine or whether we need to look at suspending our coroutine. Assuming
await_ready returns false. We proceed to running await_suspend

Several signatures are available for the await_suspend method. The simplest is void await_suspend(coroutine_handle<> handle) {...}. This is the handle for the
coroutine object that our co_await will suspend. Once this function completes,
control is returned back to caller of the coroutine object. It is this function
that is responsible for storing the coroutine handle for later so that our
coroutine does not stay suspended forever.

Once handle.resume() is called; await_ready returns false; or some other
mechanism resumes our coroutine, the method auto await_resume() is called. The
return value from await_resume is the value that the co_await operator returns.
Sometimes it is impractical for expr in co_await expr to return an awaiter
as described above. If expr returns a class the class may provide its own
instance of Awaiter operator co_await (...) which will return the Awaiter.
Alternatively one can implement an await_transform method in our promise_type which will transform expr into an Awaiter.

Now that we have described Awaiter, I would like to point out that the
initial_suspend and final_suspend methods in our promise_type both return
Awaiters. The object suspend_always and suspend_never are trivial awaiters.
suspend_always returns true to await_ready and suspend_never returns
false. There is nothing stopping you from rolling out your own though.

If you are curious what a real life Awaiter looks like, take a look at my
future object.
It stores the coroutine handle in a lamda for later processing.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)