1. Problem It Solves
An awaiter defines whether a coroutine suspends, what happens at suspension, and what value or effect appears when execution resumes. It makes an important assumption visible and checkable.
2. Prerequisites
Promise types, handles, and coroutine suspension.
You should be able to compile a short program and read its output.
3. Core Idea
The three hooks form a checkpoint: await_ready may skip it, await_suspend parks or transfers control, and await_resume supplies the continuation result. Read await_suspend as a precise promise; runtime preconditions still belong to the programmer.
4. Minimal Syntax
bool await_ready() const noexcept;
void await_suspend(std::coroutine_handle<>);
int await_resume() const;5. How It Works
The program introduces the smallest relevant form of
await_suspend.It applies the feature to fixed data while required owners remain in scope.
It prints one result that can be checked against the source.
6. Common Mistakes
Saving a handle for later requires a valid scheduler and lifetime plan; resuming inline from
await_suspendcan create subtle reentrancy.Also check the required header, C++20 library support, lifetime, and deduced types.
7. When to Use It
Use it when an external event, timer, or scheduler must integrate with coroutine suspension.
Avoid it when the operation is immediately available and ordinary function calls are clearer.
8. Simple Example
A tracing awaiter returns ready immediately so the output shows await_ready followed directly by await_resume without suspension. The companion .cpp uses no input, so its result is easy to reproduce.
Complete sample code
Source file
cpp20/46_awaitable_awaiter_suspension/main.cpp
// Day 46: Awaitables, Awaiters, and Suspension Lifecycle
#include <coroutine>
#include <iostream>
#include <utility>
struct Task {
struct promise_type;
using Handle = std::coroutine_handle<promise_type>;
struct promise_type {
Task get_return_object() { return Task{Handle::from_promise(*this)}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() noexcept {}
void unhandled_exception() { std::terminate(); }
};
Handle handle{};
explicit Task(Handle value) : handle{value} {}
Task(Task&& other) noexcept : handle{std::exchange(other.handle, {})} {}
Task(const Task&) = delete;
~Task() { if (handle) handle.destroy(); }
};
struct TraceAwaiter {
bool await_ready() const noexcept { std::cout << "await_ready\n"; return true; }
void await_suspend(std::coroutine_handle<>) const noexcept {
std::cout << "await_suspend\n";
}
int await_resume() const noexcept { std::cout << "await_resume\n"; return 7; }
};
Task demo() {
int value = co_await TraceAwaiter{};
std::cout << "value = " << value << '\n';
}
int main() { [[maybe_unused]] Task task = demo(); }
9. Key Takeaways
await_suspendexpresses the central C++20 idea of this day.The example isolates one behavior with fixed data.
Compiler checks do not replace lifetime and runtime reasoning.
Prefer the smallest interface that states the real requirement.
10. Self-Check Questions
Easy — What is the main job of
await_suspendin the minimal example?Medium — Why is
await_suspendnot called whenawait_readyreturns true?Hard — If
await_suspendstores the handle, which component becomes responsible for resuming it exactly while the frame is alive?