C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
92: Coroutines (C++20)
I've spent a huge chunk of my career writing "state machines." You know the pattern: you have a complex process—maybe a network handshake or a multi-step data parser—and because you can't block the main thread, you end up with a class full of enums like State::WaitingForHeader, State::ProcessingBody, and State::Finalizing. You then wrap everything in a giant switch statement. It works, but it's miserable to read, and the moment you need to add a single intermediate step, you're rewriting the entire flow.
Managing State by Hand
If we wanted to create a simple sequence generator—say, something that yields numbers from a sequence without calculating the whole list upfront—the "old" way is to manually track the iterator state. Look at this naive approach:
struct ManualGenerator {
int current = 0;
int limit = 10;
bool exhausted = false;
int next() {
if (current >= limit) {
exhausted = true;
return -1;
}
return current++;
}
};
// Usage
ManualGenerator gen;
while (!gen.exhausted) {
int val = gen.next();
// Do something with val
}
For a simple counter, this is fine. But imagine if the logic between those numbers involved waiting for a timer or a socket. You'd have to save every local variable into a member variable of the class just so they survive between calls to next(). You're essentially doing the compiler's job, manually hoisting local variables onto the heap (or a class instance) because the function's stack frame disappears the moment it returns.
The Magic of Suspension
C++20 coroutines change the game by letting the compiler handle that state hoisting for you. Instead of returning and exiting, a coroutine can suspend. It freezes its execution, saves its local variables and current instruction pointer into a "coroutine frame," and yields control back to the caller. When the caller decides to resume it, the function picks up exactly where it left off.
The key keywords are co_await, co_yield, and co_return. To make this work, you don't just return an int; you return a type that knows how to handle the coroutine's lifecycle (usually involving a promise_type). Here is how that same generator looks as a coroutine:
// Note: Generator is a helper type (usually from a library like cppcoro or a custom implementation)
Generator<int> coroutineGenerator() {
for (int i = 0; i < 10; ++i) {
co_yield i; // Execution pauses here and returns 'i' to the caller
}
}
// Usage
for (int val : coroutineGenerator()) {
// The loop implicitly resumes the coroutine until it finishes
}
Notice how the logic is now linear. I don't have to define a State enum or manually track current as a class member. I just write a loop. The compiler transforms this linear-looking code into the same kind of state machine I was writing by hand, but it does it perfectly and without the boilerplate.
The Hidden Cost of the Frame
Now, before you go converting every function to a coroutine, we need to talk about the trade-off. Normal functions use the stack, which is incredibly fast. Coroutines are "stackless," meaning their state (the coroutine frame) is typically allocated on the heap.
Every time you call a coroutine, you're potentially hitting the allocator. In high-frequency trading or tight embedded loops, that's a non-starter. However, the C++ compiler is allowed to perform "HALO" (Heap Allocation Elision Optimization). If the compiler can prove that the coroutine's lifetime is nested within the caller's lifetime, it can often optimize that heap allocation away. But you can't always rely on that; you have to be mindful that co_await isn't "free."
The real win here isn't performance—it's cognitive load. I can reason about an asynchronous sequence of events as a single, continuous story rather than a fragmented set of callbacks and state flags. That's where the real engineering value lies.
📋 Practical Task
Implementation: Lazy Fibonacci Sequence Generator
Your task is to implement a coroutine-based generator that produces Fibonacci numbers on demand. Instead of calculating the first N numbers and storing them in a std::vector, you will create a function that yields the next number in the sequence only when requested.
Requirements:
- Implement a function
fibonacci_generator()that usesco_yield. - The generator should run indefinitely (or until a very large limit) without crashing the stack.
- Maintain the state of the previous two Fibonacci numbers using local variables within the coroutine, demonstrating how the coroutine frame preserves state between suspensions.
- Write a main loop that consumes the first 15 numbers from this generator and prints them to the console.
Note: Since C++20 provides the coroutine infrastructure but not the high-level std::generator type (which is coming in C++23), you may use a simplified Generator wrapper provided by your environment or implement a minimal promise_type to support the co_yield keyword.
There are no comments for now.