Coroutines

This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com.

This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A C++20 coroutine is an ordinary-looking function that uses co_await, co_yield, or co_return — their mere presence makes the whole function a coroutine, compiled into a state machine that can suspend and later resume exactly where it left off, instead of running start-to-finish in one go.

co_await, co_yield, co_return

  • co_await expr — suspend until expr (an awaitable) says it’s ready, then resume with its result.

  • co_yield value — produce one value to the caller and suspend, resuming on the next request (drives generators).

  • co_return value; (or bare co_return;) — finish the coroutine, delivering value to whatever is consuming it.

Unlike an ordinary return, none of these three unwind the coroutine’s local state — that’s exactly what lets it resume later with everything still intact.

Promise Types and Awaitables

The compiler doesn’t know what "suspend" or "produce a result" mean for your coroutine on its own — a promise type (unrelated to std::promise) spells that out, and an awaitable (anything with await_ready/await_suspend/await_resume) spells out what a single co_await does:

#include <coroutine>

struct MinimalPromise {
    // required promise-type interface:
    auto get_return_object() { return std::coroutine_handle<MinimalPromise>::from_promise(*this); }
    std::suspend_always initial_suspend() { return {}; }   // suspend immediately -- caller decides when to start
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() { std::terminate(); }        // simplest possible policy: give up on an exception
};

std::suspend_always/std::suspend_never are the two trivial, standard-provided awaitables (always suspend / never suspend) — most real coroutine types (below) define their own richer ones.

A task Type

A minimal, fire-and-run "start a coroutine, don’t wait for a result" type built from the pieces above:

#include <coroutine>
#include <iostream>

struct Task {
    struct promise_type {
        Task get_return_object() { return Task{std::coroutine_handle<promise_type>::from_promise(*this)}; }
        std::suspend_never initial_suspend() { return {}; }    // start running immediately
        std::suspend_always final_suspend() noexcept { return {}; }   // stay suspended at the end -- lets
                                                                          // Task's destructor own destroying the
        void return_void() {}                                             // frame; suspend_never here would
        void unhandled_exception() { std::terminate(); }                   // auto-destroy it, and ~Task's
    };                                                                       // handle.destroy() would then be
                                                                               // a double-free

    std::coroutine_handle<promise_type> handle;
    ~Task() { if (handle) handle.destroy(); }
};

Task greet() {
    std::cout << "before suspend\n";
    co_await std::suspend_always{};    // suspends here -- something else would need to call handle.resume()
    std::cout << "after resume\n";      // to reach this line
}

A generator Type

A minimal, from-scratch lazy generator using co_yield — what a hand-written generator needs to implement, before reaching for the standard one below:

#include <coroutine>
#include <optional>

template <typename T>
struct Generator {
    struct promise_type {
        T currentValue;
        Generator get_return_object() {
            return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        std::suspend_always yield_value(T value) {   // called for every "co_yield value"
            currentValue = value;
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> handle;
    explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Generator() { if (handle) handle.destroy(); }

    bool next() {
        if (handle.done()) return false;
        handle.resume();
        return !handle.done();
    }
    T value() const { return handle.promise().currentValue; }
};

Generator<int> countUpTo(int n) {
    for (int i = 1; i <= n; ++i) {
        co_yield i;
    }
}

// Generator<int> g = countUpTo(3);
// while (g.next()) { use(g.value()); }   // 1, 2, 3

std::generator (C++23)

C++23 standardizes exactly this pattern as std::generator<T>, eliminating the promise-type boilerplate above for the common case:

#include <generator>

std::generator<int> countUpTo(int n) {
    for (int i = 1; i <= n; ++i) {
        co_yield i;
    }
}

// for (int value : countUpTo(3)) { use(value); }   // works directly with range-for -- 1, 2, 3
<generator> is standard-conformant C++23 (verified against cppreference and the working draft) but is not yet shipped by this environment’s libstdc++ 13 — it needs GCC 14+, a recent libc++, or MSVC (see Getting Started). The hand-written Task/Generator types above do compile locally, and demonstrate the exact mechanism std::generator packages.

Suspend/Resume States

stateDiagram-v2 [*] --> Created: coroutine called Created --> Suspended: initial_suspend() returns
suspend_always Created --> Running: initial_suspend() returns
suspend_never Suspended --> Running: handle.resume() called Running --> Suspended: co_await / co_yield
suspends again Running --> Done: co_return, or falls
off the end Done --> [*]: handle.destroy()