std::generator
Lazy evaluation and coroutine-based sequences
🔄 What is std::generator?
std::generator is a C++23 coroutine-based feature that creates lazy sequences of values. It generates values on-demand, making it memory-efficient for large or infinite sequences without storing all values at once.
#include <generator>
#include <iostream>
std::generator<int> fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a;
auto temp = a + b;
a = b;
b = temp;
}
}
int main() {
auto fib = fibonacci();
for (int i = 0; i < 5; ++i) {
std::cout << fib() << " ";
}
}
Output:
0 1 1 2 3
Key Features of std::generator
Lazy Evaluation
Values computed only when needed
co_yield value;
Coroutines
Built on C++20 coroutine foundation
// Pausable functions
Memory Efficient
No need to store entire sequences
// Infinite sequences OK
Range Compatible
Works with C++20 ranges and algorithms
std::ranges::take(gen, 10)
🔹 Simple Number Generator
A basic sequence generator yields values on demand using iteration protocols. In C++, this can be implemented via a coroutine using co_yield or a class with an overloaded operator(). Each call produces the next value in a sequence, such as integers from a range or terms from a mathematical series. Generators are memory-efficient for large or infinite sequences because they produce values lazily, only when requested, rather than pre-computing an entire collection upfront.
#include <generator>
#include <iostream>
std::generator<int> count_up_to(int max) {
for (int i = 1; i <= max; ++i) {
co_yield i;
}
}
int main() {
std::cout << "Counting to 5: ";
for (int value : count_up_to(5)) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}
Output:
Counting to 5: 1 2 3 4 5
🔹 Infinite Sequence
Infinite sequences leverage lazy evaluation to generate unbounded data streams. Using coroutines or iterator adaptors, you can create generators that produce values indefinitely, like all Fibonacci numbers or a stream of random numbers. The key is that values are computed only as needed, preventing memory exhaustion. These sequences are useful for simulations, real-time data feeds, mathematical conjectures, and scenarios where the upper bound is unknown or conceptually infinite.
#include <generator>
#include <iostream>
#include <ranges>
std::generator<int> powers_of_two() {
int power = 1;
while (true) {
co_yield power;
power *= 2;
}
}
int main() {
auto powers = powers_of_two();
std::cout << "First 8 powers of 2: ";
for (int value : powers | std::views::take(8)) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}
Output:
First 8 powers of 2: 1 2 4 8 16 32 64 128
🔹 String Generator
Generators can produce sequences of strings or other complex types efficiently. For example, a generator might yield formatted log lines, permutations of words, or lines read lazily from a file. By returning strings on-the-fly, the generator avoids allocating a large contiguous container, reducing memory overhead. This pattern is ideal for text processing, template rendering, lexing, and any application where string data is produced incrementally and can be processed in a streaming fashion.
#include <generator>
#include <iostream>
#include <string>
#include <vector>
std::generator<std::string> word_generator(const std::vector<std::string>& words) {
for (const auto& word : words) {
co_yield "Hello, " + word + "!";
}
}
int main() {
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
for (const auto& greeting : word_generator(names)) {
std::cout << greeting << std::endl;
}
return 0;
}
Output:
Hello, Alice! Hello, Bob! Hello, Charlie!
🔹 Recursive Generator
Recursive generators create sequences defined by self-referential rules. They are particularly useful for traversing recursive data structures like trees or graphs, or for generating fractals and recursive sequences (e.g., directory listings). In C++, this can be achieved by having a generator co_yield values from its own recursive calls. Proper implementation manages the call stack elegantly and yields results in the desired order (depth-first or breadth-first) while maintaining clarity and performance.
#include <generator>
#include <iostream>
std::generator<int> tree_traversal(int depth, int value = 1) {
co_yield value;
if (depth > 0) {
// Generate left subtree
for (int left : tree_traversal(depth - 1, value * 2)) {
co_yield left;
}
// Generate right subtree
for (int right : tree_traversal(depth - 1, value * 2 + 1)) {
co_yield right;
}
}
}
int main() {
std::cout << "Binary tree traversal (depth 2): ";
for (int node : tree_traversal(2)) {
std::cout << node << " ";
}
std::cout << std::endl;
return 0;
}
Output:
Binary tree traversal (depth 2): 1 2 4 5 3 6 7