Understanding C++ Awaitable Objects and Awaiters
In C++20 coroutines, asynchronous execution relies on suspension and
resumption mechanisms governed by awaitable types. This article explains
what an awaitable object is, how it differs from an awaiter, and the
specific requirements for implementing the three mandatory awaiter
methods: await_ready, await_suspend, and
await_resume.
What Is an Awaitable Object?
In C++, an awaitable object is any type that can be used as the
operand of the co_await operator. It represents a
computation or operation that might yield control back to the caller
before producing a result.
An object becomes awaitable in one of two ways:
- Directly Awaitable: The object itself implements the awaiter interface directly.
- Indirectly Awaitable: The object defines an
operator co_await(as a member or non-member) that returns an awaiter, or the current coroutine'spromise_typedefines anawait_transformmember function that converts the object into an awaiter.
The Awaiter Concept
While an "awaitable" is the type passed to co_await, an
"awaiter" is the actual object that the C++ runtime interacts with to
control suspension and resumption. An awaiter must define three specific
member functions:
struct MyAwaiter {
bool await_ready() noexcept;
auto await_suspend(std::coroutine_handle<> handle);
auto await_resume();
};Required Awaiter Methods
1. await_ready
- Signature:
bool await_ready() [noexcept] - Purpose: Determines whether the coroutine needs to suspend at all.
- Behavior:
- If it returns
true, the operation is already complete. The coroutine skips suspension entirely and proceeds immediately toawait_resume(). - If it returns
false, the coroutine pauses execution, preserves its state in the coroutine frame, and transfers execution toawait_suspend().
- If it returns
2. await_suspend
- Signature:
auto await_suspend(std::coroutine_handle<Promise> handle) - Purpose: Coordinates what happens once the
coroutine is suspended. It receives a
std::coroutine_handlerepresenting the suspended coroutine, which can be saved to resume the coroutine later (e.g., in an event loop or background thread). - Return Types:
void: Unconditionally keeps the coroutine suspended and returns control to the caller or resumer of the current coroutine.bool: Conditionally suspends the coroutine. Returningtruekeeps the coroutine suspended, while returningfalseimmediately resumes it on the current thread.std::coroutine_handle<...>: Performs a symmetric transfer. Control immediately transfers to the returned coroutine handle without consuming additional stack space.
3. await_resume
- Signature:
auto await_resume() - Purpose: Produces the final result of the
co_awaitexpression. - Behavior:
- This function is invoked when the coroutine resumes, or immediately
after
await_ready()ifawait_ready()returnedtrue. - The return type of
await_resume()becomes the type and value of the entireco_awaitexpression. If the operation yields no value, its return type isvoid. - If the asynchronous operation failed,
await_resume()can rethrow an exception, which will propagate out of theco_awaitexpression into the coroutine body.
- This function is invoked when the coroutine resumes, or immediately
after