std::generator (C++23)

Simplified coroutine-based sequence generation

Coroutines Recap

C++20 introduced coroutines, but they required significant boilerplate to use effectively. C++23's std::generator provides a ready-to-use implementation.

// C++20: Writing a coroutine required custom promise types
// (50+ lines of boilerplate)

// C++23: std::generator handles everything
#include <generator>
#include <iostream>

std::generator<int> count(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i;  // Yield value and suspend
    }
    // co_return is implicit at end
}

int main() {
    for (int n : count(0, 5)) {
        std::cout << n << " ";
    }
    // Output: 0 1 2 3 4
}

How It Works

co_yield

Produce a value and suspend execution. Resume when next value is requested.

Lazy Evaluation

Values are computed on-demand. Infinite sequences are possible.

Range Compatible

Works with C++20 ranges out of the box. Use with views and algorithms.

Basic Generator Examples

1. Infinite Sequences

std::generator<int> fibonacci() {
    int a = 0, b = 1;
    while (true) {
        co_yield a;
        int next = a + b;
        a = b;
        b = next;
    }
}

// Safe because of lazy evaluation
for (int n : fibonacci() | std::views::take(10)) {
    std::cout << n << " ";
}
// Output: 0 1 1 2 3 5 8 13 21 34

2. Recursive Generators

std::generator<int> tree_traversal(Node* root) {
    if (!root) co_return;
    
    // co_yield from another generator (C++23)
    for (int val : tree_traversal(root->left)) {
        co_yield val;
    }
    
    co_yield root->value;
    
    for (int val : tree_traversal(root->right)) {
        co_yield val;
    }
}

// Flatten nested structure
for (int val : tree_traversal(root)) {
    process(val);
}

3. File Line Reader

std::generator<std::string> read_lines(const std::string& path) {
    std::ifstream file(path);
    std::string line;
    
    while (std::getline(file, line)) {
        co_yield line;  // Memory-efficient line-by-line reading
    }
}

// Process large files without loading entirely into memory
for (const auto& line : read_lines("huge_file.txt")) {
    if (line.contains("ERROR")) {
        std::cout << "Found error: " << line << std::endl;
    }
}

Combining with Ranges

std::generator is a range, so it works seamlessly with C++20 views:

namespace rv = std::views;

// Generator + Ranges = Powerful composition
auto even_squares = fibonacci()
    | rv::filter([](int n) { return n % 2 == 0; })
    | rv::transform([](int n) { return n * n; })
    | rv::take(5);

for (int n : even_squares) {
    std::cout << n << " ";
}
// Output: 0 4 16 144 3600

// Complex pipeline
auto result = read_lines("data.csv")
    | rv::filter([](const auto& line) { return !line.empty(); })
    | rv::transform(parse_csv_row)
    | rv::filter([](const auto& row) { return row.is_valid; })
    | rv::take(100);

// Process only first 100 valid, non-empty rows

Generator with Input

Generators can receive input via co_yield assignment:

std::generator<int> stateful_generator() {
    int multiplier = 1;
    int value = 0;
    
    while (true) {
        // co_yield can receive input!
        if (auto input = co_yield value * multiplier) {
            // Input received, update state
            multiplier = *input;
        }
        ++value;
    }
}

// Usage
auto gen = stateful_generator();
auto it = gen.begin();

std::cout << *it << std::endl;  // 0
it = gen.send_value(2);         // Send 2, set multiplier
++it;
std::cout << *it << std::endl;  // 2 (1 * 2)
++it;
std::cout << *it << std::endl;  // 4 (2 * 2)

Performance Considerations

  • Zero overhead abstraction - generators compile to efficient state machines
  • No heap allocation for simple generators (compiler optimization)
  • Laziness avoids computing unused values
  • Coroutine frame may allocate on heap (implementation dependent)
  • Slightly more overhead than hand-written iterators

Best Practices

  • Use generators for lazy sequences, especially infinite ones
  • Combine with ranges for composable data processing
  • Prefer over eagerly computing large collections
  • Don't use when you need random access (generators are sequential)
  • Avoid co_await in generators (use async generators for that)