1. Problem It Solves
A coroutine can suspend while preserving local state, then resume later; the three keywords express waiting, yielding, and completion. It makes an important assumption visible and checkable.
2. Prerequisites
Functions, state machines, RAII, and basic templates.
You should be able to compile a short program and read its output.
3. Core Idea
The compiler rewrites the function into a resumable state machine stored in a coroutine frame. Each suspension is a bookmark. Read co_yield as a precise promise; runtime preconditions still belong to the programmer.
4. Minimal Syntax
co_await awaitable;
co_yield value;
co_return;5. How It Works
The program introduces the smallest relevant form of
co_yield.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
Keywords alone do not provide scheduling, threads, or ownership; the return type and promise determine frame behavior and cleanup.
Also check the required header, C++20 library support, lifetime, and deduced types.
7. When to Use It
Use it when a computation naturally pauses and resumes, produces a sequence, or awaits external completion.
Avoid it when a normal function or loop expresses the control flow more simply.
8. Simple Example
A minimal generator uses co_await suspend_never, yields two values, and reaches co_return; its wrapper destroys the frame. The companion .cpp uses no input, so its result is easy to reproduce.
Complete sample code
Source file
cpp20/44_coroutine_model_keywords/main.cpp
// Day 44: Coroutine Model: co_await, co_yield, and co_return
#include <coroutine>
#include <exception>
#include <iostream>
#include <utility>
struct Generator {
struct promise_type;
using Handle = std::coroutine_handle<promise_type>;
struct promise_type {
int current{};
Generator get_return_object() { return Generator{Handle::from_promise(*this)}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int value) noexcept { current = value; return {}; }
void return_void() noexcept {}
void unhandled_exception() { std::terminate(); }
};
Handle handle{};
explicit Generator(Handle value) : handle{value} {}
Generator(Generator&& other) noexcept : handle{std::exchange(other.handle, {})} {}
Generator(const Generator&) = delete;
~Generator() { if (handle) handle.destroy(); }
bool next() { handle.resume(); return !handle.done(); }
int value() const { return handle.promise().current; }
};
Generator sequence() {
co_await std::suspend_never{};
co_yield 1;
co_yield 2;
co_return;
}
int main() {
auto values = sequence();
while (values.next()) std::cout << values.value() << ' ';
std::cout << '\n';
}
9. Key Takeaways
co_yieldexpresses 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
co_yieldin the minimal example?Medium — At which statement does the caller regain control after requesting the first generated value?
Hard — Why can two coroutine return types interpret the same
co_returnsyntax differently?