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.

The co_await operator is a unary operator introduced in C++20 that suspends the execution of a coroutine, yields control back to its caller or resumer, and defines the resumption point once the awaited operation completes. It acts as the primary state-machine transition mechanism within the C++ coroutine architecture.
co_await expression;

Language Restrictions

The co_await operator is subject to strict language restrictions. It cannot be used in the following contexts:
  • The main function (which cannot be a coroutine).
  • Default arguments.
  • Constructors or destructors.
  • Catch blocks of a function-try-block.
  • The initializer of a block-scope variable with static or thread_local storage duration.
  • Functions declared as constexpr or consteval.
  • Plain functions (the presence of co_await is what transforms a function into a coroutine).

The Awaitable to Awaiter Transformation

When the compiler encounters co_await expression, it does not execute a simple function call. Instead, it resolves the Awaitable (expression) into an Awaiter object, which dictates the exact suspension and resumption behavior. The resolution follows a strict pipeline:
  1. Promise Transformation: The compiler checks the current coroutine’s promise type for the await_transform member. If the promise type declares any await_transform member, name lookup succeeds, and the compiler exclusively attempts to use it. The result of promise.await_transform(expression) becomes the new awaitable. If no overload matches the specific expression, it results in a hard compile error; it does not fall back to the original expression. The original expression is used directly only if await_transform is not declared at all in the promise type.
  2. Operator Overload: The compiler then searches for operator co_await applied to the awaitable from step 1. This follows standard C++ operator overloading rules. The compiler considers both member operator co_await functions and non-member operator co_await functions (found via Argument-Dependent Lookup) simultaneously. Standard overload resolution selects the best match. If an operator is found and successfully applied, its return value becomes the Awaiter.
  3. Fallback: If no operator co_await is found, the awaitable resulting from step 1 is treated directly as the Awaiter.

The Awaiter Interface

Once the Awaiter object is resolved, the compiler expects it to implement three specific methods: await_ready, await_suspend, and await_resume. The evaluation of co_await is mechanically expanded into the following pseudo-code, demonstrating how control flow depends on the return type of await_suspend:
{
    auto&& awaiter = get_awaiter(expression);
    
    if (!awaiter.await_ready()) {
        // 1. Suspend the coroutine (save instruction pointer, registers, local state to the heap frame)
        <suspend_coroutine> 
        
        try {
            // 'Promise' is the promise type of the current coroutine.
            // 'p' is the current coroutine's instantiated promise object.
            auto handle = std::coroutine_handle<Promise>::from_promise(p);
            using ResultT = decltype(awaiter.await_suspend(handle));
            
            // 2. Execute await_suspend and determine control flow based on its return type
            if constexpr (std::is_void_v<ResultT>) {
                awaiter.await_suspend(handle);
                <yield_to_caller_or_resumer>
            } else if constexpr (std::is_same_v<ResultT, bool>) {
                if (awaiter.await_suspend(handle)) {
                    <yield_to_caller_or_resumer>
                } else {
                    <abort_suspension_and_immediately_resume>
                }
            } else {
                // Returns std::coroutine_handle<Z>
                auto target_handle = awaiter.await_suspend(handle);
                <symmetric_transfer_to_target_handle(target_handle)>
            }
        } catch (...) {
            // If await_suspend throws, suspension is aborted, state is restored,
            // and the exception propagates immediately.
            <abort_suspension_and_restore_state>
            throw;
        }
        
        // 3. Resumption point (reached when coroutine_handle::resume() is called)
        <resume_point>
    }
    
    // 4. The co_await expression evaluates to the result of await_resume().
    // This value is yielded to the surrounding expression.
    awaiter.await_resume();
}

Mechanics of await_suspend

The most critical phase of the co_await evaluation occurs during await_suspend. The return type of this method dictates the immediate control flow after the coroutine’s state has been saved:
  • void: The coroutine remains suspended, and control is unconditionally returned to the current caller or resumer.
  • bool: If it returns true, control is returned to the caller. If it returns false, the suspension is aborted, and the current coroutine is immediately resumed. Because the coroutine is already suspended when await_suspend is called (its state has been saved to the heap frame), returning false is typically used for operations that complete concurrently during the suspension process (e.g., a failed compare-and-swap), requiring the suspension to be aborted and the coroutine to continue execution.
  • std::coroutine_handle<Z>: The current coroutine remains suspended, and execution is symmetrically transferred to the returned coroutine handle. This enables efficient tail-calls between coroutines without consuming additional stack frames.

Exception Handling Mechanics

The co_await expansion guarantees precise exception propagation depending on which part of the Awaiter interface throws:
  • If await_ready throws: The exception propagates immediately into the surrounding coroutine body. The coroutine is never suspended.
  • If await_suspend throws: The coroutine’s suspension is aborted, the coroutine is immediately resumed (state restored), and the exception is propagated out of the co_await expression. This guarantees that the coroutine state machine does not leak a suspended state if the suspension logic fails.
  • If await_resume throws: The exception propagates normally into the surrounding coroutine body. This occurs either after the coroutine has been successfully resumed, or immediately if await_ready returned true and bypassed suspension.

Standard Trivial Awaiters

The <coroutine> header provides two fundamental, empty awaiter types used to control basic suspension mechanics, often utilized within promise types:
// Never suspends. await_ready() returns true.
co_await std::suspend_never{}; 

// Always suspends. await_ready() returns false.
co_await std::suspend_always{}; 
Master C++ with Deep Grasping Methodology!Learn More