Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A coroutine in C++ is a function that can suspend execution to be resumed later, maintaining its local state across suspensions. A function becomes a coroutine implicitly if its body contains at least one of three specific keywords: co_await, co_yield, or co_return. Unlike standard functions, coroutines do not use the standard return statement and rely on a compiler-generated state machine to manage execution flow.

Coroutine Keywords

  • co_await <expr>: Evaluates an awaiter object. Suspends the coroutine’s execution only if the awaiter’s await_ready() method returns false. If it returns true, the coroutine continues execution synchronously without suspending.
  • co_yield <expr>: Syntactic sugar for co_await promise.yield_value(<expr>). Suspends the coroutine and returns a value to the caller, provided the resulting awaiter’s await_ready() method returns false.
  • co_return <expr>: Terminates the coroutine and provides a final return value (or void).

Architectural Components

C++ coroutines are stackless and rely on a tripartite architecture consisting of the Coroutine State, the Promise Object, and the Coroutine Handle.

1. Coroutine State

A compiler-generated, typically heap-allocated frame that persists across suspensions. It contains:
  • The promise object.
  • Copied parameters passed to the coroutine.
  • Local variables with a lifetime spanning a suspension point.
  • The current execution state (instruction pointer).

2. The Promise Type

The promise object is manipulated from inside the coroutine. It dictates the coroutine’s behavior, such as initialization, termination, and exception handling. The return type of a coroutine must define a nested promise_type (or specialize std::coroutine_traits).

3. The Coroutine Handle

The std::coroutine_handle<promise_type> is a non-owning pointer manipulated from outside the coroutine. The caller uses it to interact with the suspended coroutine state.
#include <coroutine>
#include <iostream>
#include <exception>

// 1. The Return Type containing the promise_type
struct MyCoroutineType {
    struct promise_type {
        int current_value;
        
        // Creates the object returned to the caller
        MyCoroutineType get_return_object() { 
            return MyCoroutineType{std::coroutine_handle<promise_type>::from_promise(*this)}; 
        }
        
        // Dictates behavior immediately after invocation
        std::suspend_always initial_suspend() { return {}; }
        
        // Dictates behavior just before destruction
        std::suspend_always final_suspend() noexcept { return {}; }
        
        // Handles co_return
        void return_void() {} 
        
        // Handles co_yield
        std::suspend_always yield_value(int val) { 
            current_value = val;
            return {}; 
        }
        
        // Catches unhandled exceptions inside the coroutine body
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> handle;
    
    MyCoroutineType(std::coroutine_handle<promise_type> h) : handle(h) {}
    MyCoroutineType(const MyCoroutineType&) = delete;
    MyCoroutineType(MyCoroutineType&& other) noexcept : handle(other.handle) {
        other.handle = nullptr;
    }
    ~MyCoroutineType() { if (handle) handle.destroy(); }
};

// 2. The Coroutine Function
MyCoroutineType example_coroutine(int param) {
    co_await std::suspend_always{}; 
    co_yield param * 2;             
    co_return;                      
}

// 3. The Caller
int main() {
    MyCoroutineType coro = example_coroutine(10);
    
    // Resumes from initial_suspend, executes until co_await
    coro.handle.resume(); 
    
    // Resumes from co_await, executes until co_yield
    coro.handle.resume(); 
    
    std::cout << "Yielded: " << coro.handle.promise().current_value << '\n';
    
    // Resumes from co_yield, executes until co_return and final_suspend
    coro.handle.resume(); 
    
    return 0;
}

The Awaiter Interface

When co_await <expr> is evaluated, <expr> must resolve to an Awaitable object. The compiler interacts with this object via the Awaiter interface to determine exactly how the suspension occurs.
#include <coroutine>
#include <iostream>
#include <exception>

struct CustomAwaiter {
    // 1. If true, suspension is skipped entirely.
    bool await_ready() const noexcept { 
        return false; 
    }

    // 2. Called immediately after suspension. 
    // Returning void unconditionally suspends the coroutine and returns control to the caller.
    // (Can also return bool to conditionally suspend, or a coroutine_handle to perform a symmetric transfer).
    void await_suspend(std::coroutine_handle<>) const noexcept {
        std::cout << "Coroutine suspended.\n";
    }

    // 3. Called when the coroutine resumes. 
    // The return value becomes the result of the co_await expression.
    int await_resume() const noexcept { 
        return 42; 
    }
};

struct DummyTask {
    struct promise_type {
        DummyTask get_return_object() { 
            return DummyTask{std::coroutine_handle<promise_type>::from_promise(*this)}; 
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> handle;

    DummyTask(std::coroutine_handle<promise_type> h) : handle(h) {}
    DummyTask(const DummyTask&) = delete;
    DummyTask(DummyTask&& other) noexcept : handle(other.handle) {
        other.handle = nullptr;
    }
    ~DummyTask() { if (handle) handle.destroy(); }
};

DummyTask awaiter_example() {
    // Suspends execution here and returns control to main.
    int result = co_await CustomAwaiter{};
    
    // Executes after main calls task.handle.resume().
    std::cout << "Awaiter result: " << result << '\n';
    co_return;
}

int main() {
    DummyTask task = awaiter_example();
    
    // Resume the coroutine from the CustomAwaiter suspension point.
    task.handle.resume();
    
    return 0;
}
Note: The standard library provides two trivial awaiters: std::suspend_always (always suspends) and std::suspend_never (never suspends).

Compiler Execution Flow

When a coroutine is invoked, the compiler injects boilerplate to manage the state machine:
  1. Allocates the coroutine state using operator new.
  2. Copies function parameters into the coroutine state.
  3. Constructs the promise_type object.
  4. Calls promise.get_return_object(). The result is kept as a local variable to be returned to the caller.
  5. Begins an implicit try-catch block.
  6. Executes co_await promise.initial_suspend() inside the try-catch block.
  7. Executes the actual body of the coroutine.
  8. If an exception escapes the body or the resume phase of initial_suspend, it is caught and routed to promise.unhandled_exception().
  9. Upon reaching co_return or the end of the body, calls promise.return_void() or promise.return_value().
  10. Destroys local variables in reverse order of creation.
  11. Exits the try-catch block.
  12. Executes co_await promise.final_suspend().
  13. State destruction depends on the behavior of final_suspend:
    • If final_suspend suspends the coroutine (e.g., returns std::suspend_always), the caller or resumer is responsible for calling .destroy() on the handle to deallocate the state.
    • If final_suspend does not suspend (e.g., returns std::suspend_never), the coroutine state is destroyed automatically upon completion. Manually calling .destroy() in this scenario results in undefined behavior (a double-free).

Restrictions

A function cannot be a coroutine if it is:
  • A constexpr function.
  • A constructor or destructor.
  • The main function.
  • A function using standard return statements (a coroutine must exclusively use co_return if it returns).
  • A function with variadic arguments (varargs), such as void foo(int, ...).
  • A function with a placeholder return type (auto or decltype(auto)).
Master C++ with Deep Grasping Methodology!Learn More