A Minimal but Complete Example

Learn how to create a fully functional coroutine example by learning about the return object, promise type, awaitable types, passing the coroutine around, and allocating its state.

Overview

Let’s start with a minimal example to understand how coroutines work. Firstly, we implement a small coroutine that is suspended and resumed before it returns:

Press + to interact
auto coroutine() -> Resumable { // Initial suspend
std::cout << "3 ";
co_await std::suspend_always{}; // Suspend (explicit)
std::cout << "5 ";
} // Final suspend then return

Secondly, we create the caller of the coroutine. Pay attention to the output and the control flow of this program. Here it is:

Press + to interact
main.cpp
chapter_12.h
Resumable.h
int main() {
std::cout << "1 ";
auto resumable = coroutine(); // Create coroutine state
std::cout << "2 ";
resumable.resume(); // Resume
std::cout << "4 ";
resumable.resume(); // Resume
std::cout << "6 ";
} // Destroy coroutine state
// Outputs: 1 2 3 4 5 6

Thirdly, the return object of the coroutine, Resumable, needs to be defined:

Press + to interact
class Resumable { // The return object
struct Promise { /*...*/ }; // Nested class, see below
std::coroutine_handle<Promise> h_;
explicit Resumable(std::coroutine_handle<Promise> h) : h_{h} {}
public:
using promise_type = Promise;
Resumable(Resumable&& r) : h_{std::exchange(r.h_, {})} {}
~Resumable() { if (h_) { h_.destroy(); } }
bool resume() {
if (!h_.done()) { h_.resume(); }
return !h_.done();
}
};

Finally, the promise type is implemented as a nested class inside the Resumable, like this:

Press + to interact
struct Promise {
Resumable get_return_object() {
using Handle = std::coroutine_handle<Promise>;
return Resumable{Handle::from_promise(*this)};
}
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};

This example is minimal but walks through a lot of things that are worth paying attention to and need to be understood:

  • The function coroutine() is a coroutine because it contains the explicit suspend/resume point using co_await
  • The coroutine doesn’t yield any values but still needs to return a type (the Resumable) with certain constraints so that the caller can resume the coroutine
  • We are using an awaitable type called std::suspend_always
  • The resume() function of the resumable object resumes the coroutine from the point it was suspended
  • The Resumable is the owner of the coroutine state. When the Resumable object is destructed, it destroys the coroutine using the coroutine_handle

The relationship between the caller, the coroutine, the coroutine handle, the promise, and the resumable is illustrated in the following diagram:

Press + to interact
Relationship between the functions/coroutines and objects involved in the resumable example
Relationship between the functions/coroutines and objects involved in the resumable example

Now it’s time to look a little closer at each part. We’ll begin with the Resumable type.

The coroutine return object

Our coroutine returns an object of type Resumable ...

Get hands-on with 1400+ tech skills courses.