This lesson uses C++23 std::generator. Compiler and standard-library support is still uneven, verify your toolchain before running the example in the playground.
A coroutine is a function that can suspend and resume. C++ implements coroutines through compiler transformation plus a promise type chosen by the return type. It is a language mechanism, not a thread, not async I/O, and not a free performance win.
The three coroutine keywords
* co_yield: suspend and produce a value (generators).
* co_return: finish the coroutine, optionally with a final value.
* co_await: suspend until an awaitable object says it is ready (used by async libraries; not required for std::generator).
You will not write promise types by hand when using std::generator; the standard library supplies them. Custom coroutine types appear in networking and async frameworks.
What the compiler builds (mental model)
When you mark a function as a coroutine, the compiler outlines the body into a coroutine frame, heap or inline storage holding local variables and suspension state. Resuming jumps back into that frame. That allocation and indirection is why coroutines shine when they replace awkward state machines, not every loop.
std::generator: lazy synchronous sequences
C++23's std::generator<T> yields values on demand. The body runs only until the next co_yield; no vector of all results is built upfront.
Lazy does not mean parallel
A generator resumes on the thread that requests the next element. It does not automatically move work to a background thread. Combine coroutines with executors or std::jthread only when you have defined threading, shutdown, and cancellation, and measure whether the complexity beats a simple loop.
Compared to iterators and ranges
Many generator use cases can use C++20 ranges views (std::views::iota | std::views::transform(...)) with zero coroutine frames. Prefer ranges when they express the logic clearly; use generators when suspension state is clearer than iterator gymnastics (parsers, tree walks, chunked producers).
When not to reach for coroutines
* Hot numeric inner loops (use plain for or SIMD, Lessons 9.8 and 9.9).
* APIs where error handling must be exception-safe across suspend points without library support.
* Codebases that cannot rely on C++23 library support yet.
Production checklist
Before shipping a coroutine API, document: who owns the coroutine frame, how errors propagate across co_yield, what happens on cancellation, and which thread resumes after suspend. Add tests that exhaust the generator, stop early, and destroy the generator mid-sequence.
Practice: write a generator that walks a std::vector<int> and yields only even values. Rewrite the same logic with a range view. Compare readability and compiler support on your toolchain.