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.

In C++20, a promise type is a compiler-mandated interface that defines the state management, control flow, and communication mechanics of a coroutine. It acts as the low-level bridge between the coroutine’s internal execution state (the coroutine frame) and the caller, dictating how the coroutine initializes, suspends, yields, returns, and handles exceptions. When the compiler encounters a coroutine (a function containing co_await, co_yield, or co_return), it determines the promise type by inspecting the coroutine’s return type T and its parameter types Args... via std::coroutine_traits<T, Args...>. By default, the compiler looks for a nested type named T::promise_type, but this mechanism allows the promise type to be specialized based on the arguments passed to the coroutine.

The Promise Type Interface

To satisfy the compiler’s coroutine state machine, a promise type must implement a specific set of methods. Below is the structural syntax of a fully defined promise type:
#include <coroutine>
#include <exception>
#include <cstddef>

struct MyCoroutineType {
    struct promise_type {
        // 1. Initialization
        MyCoroutineType get_return_object();
        std::suspend_always initial_suspend() noexcept; // or std::suspend_never

        // 2. Termination
        std::suspend_always final_suspend() noexcept;   // or std::suspend_never

        // 3. Return semantics (Must not implement both)
        void return_void(); 
        // void return_value(auto value); 

        // 4. Exception handling
        void unhandled_exception();

        // 5. Yield semantics (Optional)
        std::suspend_always yield_value(auto value);

        // 6. Await transformation (Optional)
        auto await_transform(auto expression);

        // 7. Custom allocation (Optional)
        void* operator new(std::size_t size);
        void operator delete(void* ptr, std::size_t size);
    };
};

Mechanical Breakdown of Required Methods

The compiler injects calls to the promise type’s methods at deterministic points within the coroutine’s lifecycle:
  • get_return_object(): Invoked after the coroutine frame is allocated, the coroutine parameters are copied or moved into the frame, and the promise object itself is constructed. The compiler calls this method with no arguments on the newly constructed promise object to construct the object returned to the caller. The method simply needs to return an instance of the coroutine’s return type. While it is common for types that control the coroutine to create a std::coroutine_handle here and pass it to the return object’s constructor, it is not a C++ requirement; fire-and-forget coroutines, for example, do not need to expose a handle.
  • initial_suspend(): Invoked immediately after get_return_object(). It returns an awaitable type (like std::suspend_always or std::suspend_never). If it suspends, control returns to the caller before the coroutine body executes.
  • final_suspend(): Invoked when the coroutine body finishes execution (either via completion or an unhandled exception). The C++ standard dictates that the expression promise.final_suspend() must be non-throwing, which is why the method itself must be marked noexcept. It returns an awaitable object. Suspending here keeps the coroutine frame alive, allowing the caller to extract final values or inspect the state before manually destroying the frame.
  • return_void() or return_value(T): Invoked when the compiler evaluates a co_return statement or reaches the end of the coroutine body. The C++ standard dictates that a promise type must not implement both. However, a promise type can implement neither if the coroutine never terminates (e.g., an infinite generator that never evaluates a co_return and never reaches the end of its body).
  • unhandled_exception(): Invoked if an exception escapes the coroutine body. The implementation typically captures the exception using std::current_exception() to store it in the promise state for later rethrowing.

Mechanical Breakdown of Optional Methods

  • yield_value(T): Invoked when the compiler evaluates a co_yield expression. It takes the yielded value, stores it in the promise state, and returns an awaitable to dictate whether the coroutine suspends after yielding.
  • await_transform(T): If defined, the compiler intercepts every co_await expression and transforms it by calling promise.await_transform(expression). This allows the promise type to restrict or modify what types can be awaited within the coroutine.
  • operator new / operator delete: By default, the compiler dynamically allocates the coroutine frame on the heap. However, C++20 permits Coroutine Allocation Elision (often called HALO - Heap Allocation Elision Optimization). The compiler is explicitly permitted to elide the dynamic heap allocation and place the coroutine state on the stack if it can prove the frame’s lifetime is strictly nested within the caller. If heap allocation is required and not elided, overloading these operators on the promise type overrides the default global allocator for that specific coroutine, allowing for custom memory pools or specialized allocation strategies.
Master C++ with Deep Grasping Methodology!Learn More