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.

An awaitable in C++ is any type that can serve as the operand to the co_await unary operator within a C++20 coroutine. It represents an expression whose evaluation dictates the exact mechanics of coroutine suspension, the logic executed immediately after the coroutine state is saved, and the value yielded upon coroutine resumption. To understand the mechanics of co_await, it is necessary to distinguish between an Awaitable and an Awaiter:
  • Awaitable: The original type or expression passed to co_await.
  • Awaiter: The internal object that directly implements the suspension lifecycle methods (await_ready, await_suspend, and await_resume).
Every Awaiter is an Awaitable, but an Awaitable is not necessarily an Awaiter until the compiler resolves it.

The co_await Resolution Process

When the compiler evaluates co_await expr, it must resolve the Awaitable (expr) into an Awaiter. This transformation follows a strict, sequential evaluation:
  1. Promise Transformation: The compiler first checks if the coroutine’s promise type declares a member named await_transform. If the name is found, the compiler evaluates promise.await_transform(expr). If the name is found but no overload matches expr, the program is ill-formed and results in a compile error. The fallback to expr only occurs if the name await_transform is not found at all in the promise type. Let this intermediate result be a.
  2. Operator Overload: The compiler then checks if a supports operator co_await (either as a member function of a or as an overloaded non-member function). If it does, the compiler evaluates operator co_await(a). If no such operator exists, the result remains a.
  3. Awaiter Validation: The final result of these transformations is the Awaiter object. The compiler verifies that this Awaiter type implements the three required lifecycle methods.

The Awaiter Interface

The compiler expects the resolved Awaiter object to expose the following interface:
#include <coroutine>

template <typename T>
struct CustomAwaiter {
    // 1. Determines if the coroutine should suspend at all.
    // The return type must be contextually convertible to bool.
    bool await_ready();

    // 2. Executes immediately after the coroutine is suspended.
    // Can return void, bool, or std::coroutine_handle<>.
    void await_suspend(std::coroutine_handle<> handle) noexcept;

    // 3. Executes when the coroutine resumes. 
    // The return type determines the evaluated result of the `co_await` expression.
    T await_resume(); 
};

1. await_ready()

This method returns a type that is contextually convertible to bool. If it evaluates to true, the coroutine bypasses suspension entirely, avoiding the overhead of saving the instruction pointer and registers to the already-allocated coroutine frame, and immediately proceeds to await_resume(). If it evaluates to false, the coroutine state is saved, and execution proceeds to await_suspend().

2. await_suspend(std::coroutine_handle<>)

This method is invoked after the compiler has saved the coroutine’s local variables and instruction pointer into the coroutine frame. It receives a std::coroutine_handle pointing to the suspended coroutine. The return type of await_suspend dictates the next step in the control flow:
  • void: Control returns immediately to the current caller or resumer of the coroutine.
  • bool: If true, control returns to the caller. If false, the coroutine is immediately resumed.
  • std::coroutine_handle<OtherPromise>: The compiler performs symmetric control transfer, immediately resuming the returned coroutine handle without consuming additional stack space.
Exception Handling and noexcept: According to the C++ standard ([expr.await]), if await_suspend exits via an exception, the exception is safely caught, the coroutine is restored to its resumed state, and the exception is immediately re-thrown from the co_await expression. Despite this safety mechanism, it is a strong best practice to mark await_suspend as noexcept. If await_suspend throws an exception after it has already scheduled the coroutine for resumption on another thread (or otherwise published the handle), the local exception handler will resume the coroutine while the concurrent thread also attempts to resume it. This results in a double-resume, which is Undefined Behavior (UB) and causes memory corruption.

3. await_resume()

This method is called immediately before the co_await expression completes, either after the coroutine is resumed from suspension, or directly after await_ready() if suspension was bypassed. The return type of this method becomes the evaluated result of the co_await expression.

Standard Trivial Awaiters

The <coroutine> header provides two fundamental, compiler-optimized awaiter types used to control basic suspension behavior:
namespace std {
    // Always suspends the coroutine.
    // await_ready returns false; await_suspend does nothing.
    struct suspend_always {
        constexpr bool await_ready() const noexcept { return false; }
        constexpr void await_suspend(coroutine_handle<>) const noexcept {}
        constexpr void await_resume() const noexcept {}
    };

    // Never suspends the coroutine.
    // await_ready returns true; await_suspend does nothing.
    struct suspend_never {
        constexpr bool await_ready() const noexcept { return true; }
        constexpr void await_suspend(coroutine_handle<>) const noexcept {}
        constexpr void await_resume() const noexcept {}
    };
}
Master C++ with Deep Grasping Methodology!Learn More