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 C++ coroutine is a stackless function capable of suspending execution and yielding control back to its caller, with the ability to be resumed later. Unlike regular subroutines that strictly follow a Last-In-First-Out (LIFO) execution model and destroy their stack frame upon returning, coroutines maintain their state (local variables, parameters, and execution pointers) across suspensions via a dynamically allocated coroutine state. A function is implicitly deduced as a coroutine by the compiler if its body contains at least one of the following keywords:
  • co_await: Suspends execution until an awaited computation completes.
  • co_yield: Suspends execution and yields a value to the caller.
  • co_return: Completes the coroutine execution and returns a final value (or void).
Certain functions are strictly forbidden from being coroutines: the main function, constexpr functions, constructors, and destructors.

The Coroutine Machinery

C++ does not provide built-in coroutine types like Task or Generator. Instead, it provides a low-level framework consisting of three primary components that interact to manage the coroutine’s lifecycle: 1. The Promise Object Manipulated from inside the coroutine. It acts as the communication channel between the coroutine and the caller. It dictates the coroutine’s behavior upon initialization, suspension, exception handling, and completion. The compiler resolves the promise type using std::coroutine_traits<ReturnType, Args...>. While the default implementation of this trait looks for a nested ReturnType::promise_type, developers can specialize std::coroutine_traits to adapt types that do not have a nested promise_type (such as standard library types or third-party classes). 2. The Coroutine Handle (std::coroutine_handle) Manipulated from outside the coroutine. It is a non-owning pointer to the compiler-generated coroutine state. The caller uses the handle to resume execution (handle.resume()) or explicitly destroy the coroutine frame (handle.destroy()). 3. The Awaitable and Awaiter Objects that dictate the semantics of a suspension point. When co_await <expr> is evaluated, <expr> evaluates to an Awaitable. The compiler then converts this Awaitable into an Awaiter (either via promise_type::await_transform(<expr>), an operator co_await() overload, or by the Awaitable already being a valid Awaiter). The resulting Awaiter must implement three methods:
  • await_ready(): Returns a boolean. If true, suspension is skipped.
  • await_suspend(handle): Called immediately after suspension. Can return void, bool, or another coroutine_handle to transfer control.
  • await_resume(): Called when the coroutine is resumed. Its return value is the result of the co_await expression.

Syntax and Structural Implementation

To write a coroutine, you must define a return type that resolves to a valid promise type. Because the return type typically manages the lifecycle of the std::coroutine_handle, it must strictly adhere to the Rule of Five to prevent undefined behavior such as double-freeing the coroutine state.
#include <coroutine>
#include <exception>
#include <utility>

// 1. The Return Type
struct Task {
    // 2. The Promise Type (Default coroutine_traits looks for this nested type)
    struct promise_type {
        // Creates the return object passed to the caller
        Task get_return_object() {
            return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        
        // Dictates behavior immediately after coroutine creation
        std::suspend_always initial_suspend() noexcept { return {}; }
        
        // Dictates behavior immediately before coroutine destruction
        std::suspend_always final_suspend() noexcept { return {}; }
        
        // Handles co_return;
        void return_void() noexcept {}
        
        // Handles uncaught exceptions within the coroutine body
        void unhandled_exception() {
            std::terminate();
        }
    };

    std::coroutine_handle<promise_type> handle;

    // Constructor binds the handle
    explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}
    
    // Destructor cleans up the heap-allocated coroutine state
    ~Task() { 
        if (handle) handle.destroy(); 
    }

    // Delete copy operations to prevent double-free of the coroutine state
    Task(const Task&) = delete;
    Task& operator=(const Task&) = delete;

    // Provide move operations that nullify the moved-from handle
    Task(Task&& other) noexcept : handle(std::exchange(other.handle, nullptr)) {}
    Task& operator=(Task&& other) noexcept {
        if (this != &other) {
            if (handle) handle.destroy();
            handle = std::exchange(other.handle, nullptr);
        }
        return *this;
    }
};

// 3. The Coroutine Function
Task execute_process() {
    // 4. The Suspension Point (std::suspend_always is a standard Awaiter)
    co_await std::suspend_always{};
    
    co_return;
}

Execution Flow

When a coroutine is invoked, the compiler injects boilerplate code to manage the state machine. The exact sequence of operations is:
  1. Allocation: The compiler allocates the coroutine state (typically on the heap, though the compiler may optimize this away via Heap Allocation Elision if the lifetime is strictly scoped).
  2. Initialization: Function parameters are copied/moved into the coroutine state.
  3. Promise Construction: The promise_type object is constructed inside the coroutine state.
  4. Return Object Creation: promise.get_return_object() is called. The result is kept as a local variable to be returned to the caller later.
  5. Initial Suspend: co_await promise.initial_suspend() is executed. If it suspends (e.g., returns std::suspend_always), control returns to the caller, yielding the return object.
  6. Execution: The caller resumes the coroutine via the handle. The coroutine body executes until it hits a co_await or co_yield.
  7. Completion: Upon hitting co_return, promise.return_void() or promise.return_value() is called.
  8. Final Suspend: co_await promise.final_suspend() is executed. It is highly recommended to suspend here so the caller can retrieve final values from the promise before the state is destroyed.
  9. Destruction: The coroutine state is destroyed, either implicitly if it falls off the end without a final suspend, or explicitly via handle.destroy().
Master C++ with Deep Grasping Methodology!Learn More