Ch22: Modern Control Flow — Coroutines & Modules¶
TL;DR: Coroutines & Suspended Execution: C++20 coroutines pause execution via
co_await,co_yield, orco_returnusing apromise_typeto manage state preservation and handle resumption. |std::format(C++20) gives Python-style, type-safe string formatting as a safer alternative toprintf;<random>provides an engine + distribution pair that replacesrand()with statistically sound, seedable random number generation.
⚡ Quick Reference¶
#include <iostream>
#include <coroutine>
struct Task {
struct promise_type {
Task get_return_object() {
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
std::coroutine_handle<promise_type> coro_handle;
};
Task my_coroutine() {
co_await std::suspend_always{};
co_return;
}
int main() {
Task t = my_coroutine();
t.coro_handle.resume();
bool completed = t.coro_handle.done();
t.coro_handle.destroy();
return 0;
}
Formatted Output & Random Distributions¶
#include <iostream>
#include <format> // C++20 std::format (standard-library version of {fmt})
#include <random>
int main() {
// std::format - type-safe, positional/named formatting, returns a std::string
std::string msg = std::format("{} is {} years old", "Alice", 30);
std::cout << msg << '\n';
// Width, precision, and fill specifiers
std::cout << std::format("{:>10.2f}", 3.14159) << '\n'; // right-aligned, 2 decimals
// --- Random numbers ---
// std::random_device - non-deterministic seed source (hardware entropy if available)
std::random_device rd;
// std::mt19937 - Mersenne Twister engine, seeded once, reused across calls
std::mt19937 gen(rd());
// std::uniform_int_distribution - maps engine output to a specific range/shape
std::uniform_int_distribution<int> diceRoll(1, 6);
std::cout << "Dice roll: " << diceRoll(gen) << '\n';
std::uniform_real_distribution<double> percent(0.0, 100.0);
std::cout << "Random %: " << percent(gen) << '\n';
return 0;
}
🧠 Core Concepts¶
- Coroutines & Suspended Execution: C++20 coroutines pause execution via
co_await,co_yield, orco_returnusing apromise_typeto manage state preservation and handle resumption. - Modular Compilation Units: Modules replace header files with compile-time isolation units (
export module) that specify public interfaces viaexport, accelerating compile times. - Advanced Module Partitioning: Modules support hierarchical sub-modules (
math.add_sub) and interface partitions (math:addition) to enable fine-grained, parallel translation compiles. std::formatvsprintf:std::format(C++20, standardized from the third-party{fmt}library) checks format strings at compile time when possible, is type-safe (no%d/%smismatches causing UB), and supports positional arguments and rich formatting specs like{:>10.2f}.- Engine + Distribution Separation:
<random>deliberately splits "generate raw random bits" (the engine, e.g.std::mt19937) from "shape those bits into a useful range/distribution" (the distribution, e.g.std::uniform_int_distribution). This is whyrand()is discouraged — it conflates both roles poorly and has known statistical bias. - Seeding:
std::random_deviceprovides a one-time, non-deterministic seed. The engine itself should be created once and reused for all subsequent random draws — recreating it (or reseeding) per-call is a common performance and correctness mistake.
⚠️ Pitfalls (Quick Scan)¶
| Mistake | Fix |
|---|---|
Calling .resume() on a completed coroutine |
Always verify .done() returns false before resuming |
Returning suspend_never from final_suspend() in custom generators |
Return suspend_always to inspect state safely post-completion |
Placing #include directives after module declarations |
Always place standard headers inside the global module fragment (module; block) |
Splitting template class implementations into separate implementation .cpp files |
Define all template class methods inline inside interface module files (.ixx) |
Marking member variables or static definitions with export inside class scopes |
Apply export specifiers only to module-level namespace declarations |
| Using anonymous namespaces inside exported blocks | Export individual namespace symbols directly at module level |
Using printf-style %d/%s format strings with mismatched types |
Prefer std::format, which is compile-time checked and type-safe |
Creating a new std::mt19937 engine every time you need a random number |
Construct the engine once, reuse it across all draws |
Using std::random_device directly for repeated random draws |
It's meant only for seeding — use it once to seed an engine, then draw from the engine |
Still using rand() % N for "random" ranges |
rand()'s low bits are less random and range isn't uniform — use <random> distributions instead |
Assuming std::format mutates/prints directly |
It returns a std::string; you still need std::cout << or std::print (C++23) to output it |
📖 Full Details¶
Cause → Effect → Fix with timestamp (click to expand)
* **Calling `.resume()` on a completed coroutine** -> **Undefined behavior or severe memory corruption crashes** -> **Always verify `.done()` returns false before resuming (51:00)** * **Returning `suspend_never` from `final_suspend()` in custom generators** -> **Destroys coroutine frames immediately, turning handles dangling before inspection** -> **Return `suspend_always` to inspect state safely post-completion (53:00)** * **Placing `#include` directives after module declarations** -> **Compiler ignores includes or causes compile-time syntax errors** -> **Always place standard headers inside the global module fragment (`module;` block) (111:00)** * **Splitting template class implementations into separate implementation `.cpp` files** -> **Linker errors stating undefined reference because template code must be visible** -> **Define all template class methods inline inside interface module files (`.ixx`) (119:00)** * **Marking member variables or static definitions with `export` inside class scopes** -> **Compile-time syntax errors** -> **Apply `export` specifiers only to module-level namespace declarations (117:00)** * **Using anonymous namespaces inside exported blocks** -> **Compile-time error because anonymous namespace elements cannot be exported** -> **Export individual namespace symbols directly at module level (118:00)** * **Using `printf`-style format strings with mismatched argument types** -> **Undefined behavior at runtime since the compiler cannot verify `%d` actually receives an `int`, often silently printing garbage or crashing** -> **Use `std::format`, whose format string is checked against argument types (compile-time checked in C++20 when the format string is a constant expression)** * **Constructing a new `std::mt19937` engine on every random draw** -> **Each call reseeds from scratch, which is both slow (engine initialization has real cost) and can produce correlated/less-random sequences if seeded from a low-entropy or time-based source repeatedly** -> **Create the engine once (e.g. as a local static or class member) and reuse it for every subsequent draw** * **Calling `std::random_device` repeatedly for each random number instead of just for seeding** -> **`random_device` is often slow (may query OS-level entropy) and isn't intended as a general-purpose generator** -> **Use `random_device` exactly once to seed a `std::mt19937` (or similar engine), then draw all further numbers from that engine** * **Continuing to use legacy `rand() % N` for generating a bounded random value** -> **The modulo operation introduces statistical bias (lower values are slightly more likely when `RAND_MAX + 1` isn't a multiple of `N`), and `rand()`'s lower bits have known poor randomness in many implementations** -> **Use `std::uniform_int_distribution📎 Repo Files¶
48.Coroutines/47.5CoAwait/main.cpp48.Coroutines/47.6CoYield/main.cpp48.Coroutines/47.7CoReturn/main.cpp48.Coroutines/47.8CustomGenerator/main.cpp48.Coroutines/47.9ThirdPartyCorotineTypes/main.cpp49.Modules/48.1Introduction/main.cpp49.Modules/48.2YourFirstModule/main.cpp49.Modules/48.3BlockExport/main.cpp49.Modules/48.4SeparateInterfaceFromImplementation-SameFile/main.cpp49.Modules/48.5SeparateInterfaceFromImplementation-DifferentFiles/main.cpp49.Modules/48.6MultipleImplementationFiles/main.cpp49.Modules/48.7MultipleInterfaceFiles/main.cpp49.Modules/48.8ExportImport/main.cpp49.Modules/48.9SubModules/main.cpp49.Modules/48.10ModuleInterfacePartitions/main.cpp49.Modules/48.11BoxContainerModule/main.cpp49.Modules/48.12ModulesWithNamespaces/main.cpp49.Modules/48.13VisibilityReachability/main.cpp49.Modules/48.14PrivateModuleFragment/main.cpp49.Modules/48.15DosAndDonts/main.cpp- (No dedicated repo folder yet — supplementary topic added from gap analysis)