1. Problem It Solves
A generator exposes a sequence one element at a time without allocating and filling an entire result container first. It makes an important assumption visible and checkable.
2. Prerequisites
Coroutine frames, handles, and yield suspension.
You should be able to compile a short program and read its output.
3. Core Idea
It is a paused producer. Every request resumes the producer until the next co_yield, then the saved frame waits with its local state intact. Read co_yield as a precise promise; runtime preconditions still belong to the programmer.
4. Minimal Syntax
for (int value = first; value <= last; ++value) co_yield value;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
References to yielded internal values are valid only under the generator’s protocol, and abandoning a generator still requires destroying its frame.
Also check the required header, C++20 library support, lifetime, and deduced types.
7. When to Use It
Use it when values are computed on demand, may be numerous, or may stop early.
Avoid it when all values are small, already available, and ownership in a container is simpler.
8. Simple Example
The generator counts produced values and proves that none are produced until the caller requests them. The companion .cpp uses no input, so its result is easy to reproduce.
Complete sample code
Source file
cpp20/47_generator_lazy_streams/main.cpp
// Day 47: Generators and Lazy Data Streams
#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 numbers(int& produced) {
for (int value = 1; value <= 3; ++value) {
++produced;
co_yield value;
}
}
int main() {
int produced = 0;
auto stream = numbers(produced);
std::cout << "before = " << produced << '\n';
stream.next(); std::cout << stream.value() << '\n';
stream.next(); std::cout << stream.value() << '\n';
std::cout << "after two = " << produced << '\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 — What is the production counter before the first resume and after requesting two values?
Hard — Why can retaining a reference to
promise.currentacross the next resume observe a changed value?