cpp-know-hows

cpp related stuff

View on GitHub

Coroutines (C++20)

Overview

C++20 introduced coroutines as a powerful feature for writing asynchronous and generator-style code. Coroutines are functions that can suspend and resume execution, enabling elegant solutions for async operations, lazy evaluation, and cooperative multitasking.

┌────────────────────────────────────────────────────────┐
│                  COROUTINES SYSTEM                     │
├────────────────────────────────────────────────────────┤
│                                                        │
│  KEYWORDS       │  COMPONENTS      │  USE CASES        │
│  ────────       │  ──────────      │  ─────────        │
│  • co_await     │  • Promise type  │  • Generators     │
│  • co_yield     │  • Awaitable     │  • Async tasks    │
│  • co_return    │  • Coroutine     │  • State machines │
│                 │    handle        │  • Lazy eval      │
│                                                        │
│  ADVANTAGES:                                           │
│  • Cleaner async code (no callback hell)               │
│  • Composable operations                               │
│  • Memory efficient (no thread per task)               │
│  • Natural control flow                                │
│                                                        │
└────────────────────────────────────────────────────────┘

Coroutine Basics

What Makes a Coroutine?

// A function is a coroutine if it uses any of:
// 1. co_await
// 2. co_yield
// 3. co_return

#include <coroutine>

// Example 1: Using co_return
Task simple_coroutine() {
    co_return;  // This makes it a coroutine
}

// Example 2: Using co_yield
Generator<int> count_to_three() {
    co_yield 1;
    co_yield 2;
    co_yield 3;
}

// Example 3: Using co_await
Task async_operation() {
    auto result = co_await some_async_task();
    co_return result;
}

Coroutine Execution Flow

Normal Function:
┌─────────────────────────────────────┐
│ Start → Execute → Return → End      │
└─────────────────────────────────────┘

Coroutine:
┌─────────────────────────────────────────────────────┐
│ Start → Execute → Suspend ─┐                        │
│           ↑                 │                       │
│           └─── Resume ←─────┘                       │
│                 Execute → Suspend ─┐                │
│                   ↑                │                │
│                   └─── Resume ←────┘                │
│                       Execute → Complete → End      │
└─────────────────────────────────────────────────────┘

Key: Can pause and resume multiple times

Generator Pattern

Simple Generator

#include <coroutine>
#include <iostream>
#include <exception>

// Generator return type
template<typename T>
struct Generator {
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    
    struct promise_type {
        T current_value;
        std::exception_ptr exception_;
        
        Generator get_return_object() {
            return Generator{handle_type::from_promise(*this)};
        }
        
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        
        std::suspend_always yield_value(T value) {
            current_value = std::move(value);
            return {};
        }
        
        void return_void() {}
        
        void unhandled_exception() {
            exception_ = std::current_exception();
        }
    };
    
    handle_type h_;
    
    Generator(handle_type h) : h_(h) {}
    ~Generator() { if (h_) h_.destroy(); }
    
    // Disable copy
    Generator(const Generator&) = delete;
    Generator& operator=(const Generator&) = delete;
    
    // Enable move
    Generator(Generator&& other) noexcept : h_(other.h_) {
        other.h_ = {};
    }
    
    Generator& operator=(Generator&& other) noexcept {
        if (this != &other) {
            if (h_) h_.destroy();
            h_ = other.h_;
            other.h_ = {};
        }
        return *this;
    }
    
    // Iterator interface
    struct iterator {
        handle_type h_;
        
        iterator& operator++() {
            h_.resume();
            return *this;
        }
        
        bool operator==(std::default_sentinel_t) const {
            return !h_ || h_.done();
        }
        
        T const& operator*() const {
            return h_.promise().current_value;
        }
    };
    
    iterator begin() {
        if (h_) h_.resume();
        return iterator{h_};
    }
    
    std::default_sentinel_t end() { return {}; }
};

// Usage
Generator<int> counter(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i;
    }
}

int main() {
    for (int value : counter(1, 5)) {
        std::cout << value << '\n';  // Prints: 1 2 3 4
    }
    
    return 0;
}

Infinite Generator

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

void example() {
    auto fib = fibonacci();
    auto it = fib.begin();
    
    // Take first 10 Fibonacci numbers
    for (int i = 0; i < 10; ++i) {
        std::cout << *it << ' ';
        ++it;
    }
    // Output: 0 1 1 2 3 5 8 13 21 34
}

Async Task Pattern

Task Implementation

#include <coroutine>
#include <exception>
#include <variant>

template<typename T>
struct Task {
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    
    struct promise_type {
        std::variant<std::monostate, T, std::exception_ptr> result_;
        std::coroutine_handle<> continuation_;
        
        Task get_return_object() {
            return Task{handle_type::from_promise(*this)};
        }
        
        std::suspend_always initial_suspend() noexcept { return {}; }
        
        struct final_awaiter {
            bool await_ready() noexcept { return false; }
            
            void await_suspend(handle_type h) noexcept {
                if (auto continuation = h.promise().continuation_) {
                    continuation.resume();
                }
            }
            
            void await_resume() noexcept {}
        };
        
        final_awaiter final_suspend() noexcept { return {}; }
        
        void return_value(T value) {
            result_ = std::move(value);
        }
        
        void unhandled_exception() {
            result_ = std::current_exception();
        }
    };
    
    handle_type h_;
    
    Task(handle_type h) : h_(h) {}
    ~Task() { if (h_) h_.destroy(); }
    
    // Move only
    Task(Task&& other) noexcept : h_(other.h_) {
        other.h_ = {};
    }
    
    Task& operator=(Task&& other) noexcept {
        if (this != &other) {
            if (h_) h_.destroy();
            h_ = other.h_;
            other.h_ = {};
        }
        return *this;
    }
    
    // Awaitable interface
    bool await_ready() { return false; }
    
    void await_suspend(std::coroutine_handle<> continuation) {
        h_.promise().continuation_ = continuation;
        h_.resume();
    }
    
    T await_resume() {
        auto& result = h_.promise().result_;
        if (std::holds_alternative<std::exception_ptr>(result)) {
            std::rethrow_exception(std::get<std::exception_ptr>(result));
        }
        return std::get<T>(result);
    }
};

// Specialization for void
template<>
struct Task<void> {
    // Similar implementation for void
};

Using Tasks

Task<int> async_compute() {
    // Simulate async work
    co_await std::suspend_always{};
    co_return 42;
}

Task<int> async_workflow() {
    int result1 = co_await async_compute();
    int result2 = co_await async_compute();
    co_return result1 + result2;
}

void example() {
    auto task = async_workflow();
    // Task is lazy - doesn't start until awaited
    
    // Execute task
    auto handle = std::coroutine_handle<decltype(task)::promise_type>::from_promise(
        task.h_.promise()
    );
    handle.resume();
}

Awaitable Objects

Custom Awaitable

#include <chrono>
#include <thread>

struct sleep_awaitable {
    std::chrono::milliseconds duration_;
    
    sleep_awaitable(std::chrono::milliseconds duration) 
        : duration_(duration) {}
    
    bool await_ready() const noexcept {
        return duration_.count() <= 0;
    }
    
    void await_suspend(std::coroutine_handle<> handle) const {
        std::thread([handle, duration = duration_]() {
            std::this_thread::sleep_for(duration);
            handle.resume();
        }).detach();
    }
    
    void await_resume() const noexcept {}
};

// Helper function
auto sleep_for(std::chrono::milliseconds duration) {
    return sleep_awaitable{duration};
}

// Usage
Task<void> async_sleep_example() {
    std::cout << "Starting\n";
    co_await sleep_for(std::chrono::seconds(1));
    std::cout << "After 1 second\n";
    co_await sleep_for(std::chrono::seconds(2));
    std::cout << "After 3 seconds total\n";
}

Awaitable Transformation

struct transform_awaitable {
    int value_;
    
    bool await_ready() { return false; }
    
    void await_suspend(std::coroutine_handle<>) {}
    
    int await_resume() {
        return value_ * 2;  // Transform the result
    }
};

Task<int> example_transform() {
    int result = co_await transform_awaitable{21};
    co_return result;  // Returns 42
}

Promise Type

Understanding Promise Type

Coroutine Components:
┌─────────────────────────────────────────────────────┐
│                                                     │
│  ┌─────────────┐                                    │
│  │  Coroutine  │                                    │
│  │   Frame     │  Stores local variables            │
│  │             │  and suspend state                 │
│  └──────┬──────┘                                    │
│         │                                           │
│         ▼                                           │
│  ┌─────────────┐                                    │
│  │   Promise   │                                    │
│  │    Type     │  Controls coroutine behavior       │
│  │             │  - Initial/final suspend           │
│  │             │  - Return value handling           │
│  └──────┬──────┘  - Exception handling              │
│         │                                           │
│         ▼                                           │
│  ┌─────────────┐                                    │
│  │  Coroutine  │                                    │
│  │   Handle    │  Controls resume/destroy           │
│  │             │                                    │
│  └─────────────┘                                    │
│                                                     │
└─────────────────────────────────────────────────────┘

Promise Type Methods

struct my_promise_type {
    // 1. Create return object
    auto get_return_object() {
        return MyCoroutineType{
            std::coroutine_handle<my_promise_type>::from_promise(*this)
        };
    }
    
    // 2. Initial suspend point
    auto initial_suspend() {
        return std::suspend_always{};  // Lazy start
        // or std::suspend_never{};    // Eager start
    }
    
    // 3. Final suspend point
    auto final_suspend() noexcept {
        return std::suspend_always{};  // Keep alive
        // or std::suspend_never{};    // Auto destroy
    }
    
    // 4. Handle return value
    void return_value(T value) {
        // Store value for retrieval
    }
    // OR
    void return_void() {
        // For coroutines with no return
    }
    
    // 5. Handle exceptions
    void unhandled_exception() {
        // Store or rethrow exception
    }
    
    // 6. Handle co_yield
    auto yield_value(T value) {
        // Store value
        return std::suspend_always{};
    }
};

Practical Examples

Async File Reader

#include <fstream>
#include <string>
#include <vector>

Generator<std::string> read_lines(const std::string& filename) {
    std::ifstream file(filename);
    std::string line;
    
    while (std::getline(file, line)) {
        co_yield line;
    }
}

void example() {
    for (const auto& line : read_lines("data.txt")) {
        std::cout << line << '\n';
    }
}

Range Generator

Generator<int> range(int start, int end, int step = 1) {
    for (int i = start; i < end; i += step) {
        co_yield i;
    }
}

void example() {
    // Like Python's range()
    for (int i : range(0, 10, 2)) {
        std::cout << i << ' ';  // 0 2 4 6 8
    }
}

Lazy Filter

template<typename Gen, typename Pred>
Generator<typename Gen::value_type> filter(Gen gen, Pred pred) {
    for (auto value : gen) {
        if (pred(value)) {
            co_yield value;
        }
    }
}

void example() {
    auto numbers = range(1, 20);
    auto evens = filter(std::move(numbers), [](int x) { return x % 2 == 0; });
    
    for (int n : evens) {
        std::cout << n << ' ';  // 2 4 6 8 10 12 14 16 18
    }
}

Lazy Map

template<typename Gen, typename Func>
auto map(Gen gen, Func func) -> Generator<decltype(func(*gen.begin()))> {
    for (auto value : gen) {
        co_yield func(value);
    }
}

void example() {
    auto numbers = range(1, 6);
    auto squares = map(std::move(numbers), [](int x) { return x * x; });
    
    for (int n : squares) {
        std::cout << n << ' ';  // 1 4 9 16 25
    }
}

State Machine

enum class State { Init, Processing, Complete };

Generator<State> state_machine() {
    co_yield State::Init;
    
    // Initialization logic
    std::cout << "Initializing...\n";
    co_yield State::Processing;
    
    // Processing logic
    std::cout << "Processing...\n";
    co_yield State::Processing;
    
    // More processing
    std::cout << "Finishing...\n";
    co_yield State::Complete;
}

void example() {
    auto sm = state_machine();
    
    for (auto state : sm) {
        std::cout << "Current state: " << static_cast<int>(state) << '\n';
        // Can add logic based on state
    }
}

Async Networking Example

TCP Echo Server

Task<void> handle_client(Socket socket) {
    char buffer[1024];
    
    while (true) {
        // Async read
        int bytes_read = co_await socket.async_read(buffer, sizeof(buffer));
        
        if (bytes_read <= 0) break;
        
        // Async write (echo back)
        co_await socket.async_write(buffer, bytes_read);
    }
    
    co_await socket.close();
}

Task<void> run_server(int port) {
    auto listener = co_await create_listener(port);
    
    while (true) {
        auto client = co_await listener.accept();
        
        // Handle each client concurrently
        handle_client(std::move(client));  // Fire and forget
    }
}

Coroutine Patterns

Lazy Evaluation

// Values computed only when requested
Generator<int> expensive_sequence() {
    for (int i = 0; i < 1000000; ++i) {
        // Expensive computation
        int result = compute_expensive(i);
        co_yield result;
    }
}

void example() {
    auto seq = expensive_sequence();
    auto it = seq.begin();
    
    // Only compute first 3 values
    for (int i = 0; i < 3; ++i) {
        std::cout << *it << '\n';
        ++it;
    }
    // Rest never computed!
}

Pipeline Pattern

auto pipeline = 
    range(1, 100)
    | filter([](int x) { return x % 2 == 0; })
    | map([](int x) { return x * x; })
    | take(10);

for (int value : pipeline) {
    std::cout << value << ' ';
}

Async Retry Logic

Task<Response> retry_async(int max_attempts) {
    for (int attempt = 1; attempt <= max_attempts; ++attempt) {
        try {
            auto response = co_await make_request();
            co_return response;
        }
        catch (const NetworkError& e) {
            if (attempt == max_attempts) throw;
            
            // Exponential backoff
            auto delay = std::chrono::milliseconds(100 * (1 << attempt));
            co_await sleep_for(delay);
        }
    }
}

Performance Considerations

Memory Overhead

Traditional Callback:
┌────────────────────────┐
│ Heap allocations for   │
│ callback objects       │  ~50-100 bytes per callback
└────────────────────────┘

Thread per Task:
┌────────────────────────┐
│ Thread stack           │  ~1-8 MB per thread
│ OS scheduler overhead  │
└────────────────────────┘

Coroutine:
┌────────────────────────┐
│ Coroutine frame        │  ~100-500 bytes
│ Promise object         │  Minimal overhead
└────────────────────────┘

Coroutines: Much more memory efficient than threads

Allocation Elision

// Compiler may optimize away heap allocation
Task<int> small_coroutine() {
    co_return 42;
}

// HALO (Heap Allocation eLision Optimization)
// If coroutine lifetime is obvious, compiler can
// allocate on stack instead of heap

Best Practices

1. Use Coroutines for I/O-Bound Operations

// GOOD: I/O-bound async operations
Task<Data> fetch_data_async(const std::string& url) {
    auto response = co_await http_get(url);
    co_return parse(response);
}

// BAD: CPU-bound operations (use threads instead)
// Coroutines don't provide parallelism, just concurrency

2. Avoid Capturing References

// BAD: Captured reference may dangle
Task<void> dangerous(int& value) {
    co_await something();
    value = 42;  // 'value' may be destroyed!
}

// GOOD: Pass by value or use shared_ptr
Task<void> safe(std::shared_ptr<int> value) {
    co_await something();
    *value = 42;  // Safe
}

3. Be Careful with Lifetimes

// BAD: Coroutine outlives local variable
void bad_example() {
    std::string data = "important data";
    auto task = process_data(data);  // Coroutine captures reference
    // task may resume after this function returns!
}

// GOOD: Ensure proper lifetime
Task<void> good_example() {
    std::string data = "important data";
    co_await process_data(data);  // Wait for completion
}

4. Use Structured Concurrency

// Launch multiple coroutines and wait for all
Task<void> process_batch(const std::vector<Item>& items) {
    std::vector<Task<Result>> tasks;
    
    for (const auto& item : items) {
        tasks.push_back(process_item(item));
    }
    
    // Wait for all to complete
    for (auto& task : tasks) {
        co_await task;
    }
}

Common Pitfalls

1. Forgetting co_await

// BAD: Returns Task, doesn't execute
Task<int> compute();

void bad() {
    auto task = compute();  // Created but not executed
    // Forgot co_await!
}

// GOOD: Actually execute
Task<void> good() {
    int result = co_await compute();  // Executes
}

2. Dangling References

// BAD
Generator<int> bad_generator() {
    std::vector<int> vec = {1, 2, 3};
    for (int value : vec) {
        co_yield value;  // vec destroyed after first suspend!
    }
}

// GOOD: Ensure data lifetime
Generator<int> good_generator() {
    std::vector<int> vec = {1, 2, 3};
    for (int i = 0; i < vec.size(); ++i) {
        co_yield vec[i];  // Copy value before yielding
    }
}

3. Exception Safety

// Ensure exceptions in coroutines are handled
Task<void> example() {
    try {
        co_await may_throw();
    }
    catch (const std::exception& e) {
        // Handle error
        std::cerr << "Error: " << e.what() << '\n';
    }
}

Compiler Support

Current Status (2024)

┌────────────────────────────────────────────────────────┐
│              Coroutine Support Status                  │
├────────────────────────────────────────────────────────┤
│ Compiler      │ Version  │ Support                     │
├───────────────┼──────────┼─────────────────────────────┤
│ GCC           │ 10+      │ Full support                │
│ Clang         │ 14+      │ Full support                │
│ MSVC          │ 19.28+   │ Full support                │
│                                                        │
│ Note: Requires -std=c++20 or later                     │
│                                                        │
│ Libraries:                                             │
│ • cppcoro: Popular coroutine utilities library         │
│ • folly: Facebook's coroutine support                  │
│ • boost.asio: Coroutine integration                    │
└────────────────────────────────────────────────────────┘

Libraries and Frameworks

cppcoro

#include <cppcoro/task.hpp>
#include <cppcoro/sync_wait.hpp>

cppcoro::task<int> example() {
    co_return 42;
}

int main() {
    int result = cppcoro::sync_wait(example());
    std::cout << result << '\n';
}

Boost.Asio Coroutines

#include <boost/asio.hpp>
#include <boost/asio/co_spawn.hpp>

boost::asio::awaitable<void> echo(tcp::socket socket) {
    try {
        char data[1024];
        for (;;) {
            std::size_t n = co_await socket.async_read_some(
                boost::asio::buffer(data),
                boost::asio::use_awaitable
            );
            
            co_await async_write(socket,
                boost::asio::buffer(data, n),
                boost::asio::use_awaitable
            );
        }
    }
    catch (std::exception& e) {
        // Handle error
    }
}

Future Directions

C++23 and Beyond


Resources


Complete Practical Example: Async Data Processing Pipeline

Here’s a comprehensive example integrating generators, async tasks, and lazy evaluation:

#include <coroutine>
#include <iostream>
#include <vector>
#include <string>
#include <optional>
#include <chrono>
#include <thread>
#include <map>

// Generator implementation (from earlier in chapter)
template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        
        Generator get_return_object() {
            return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        std::suspend_always yield_value(T value) {
            current_value = std::move(value);
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
    
    using handle_type = std::coroutine_handle<promise_type>;
    handle_type h_;
    
    Generator(handle_type h) : h_(h) {}
    ~Generator() { if (h_) h_.destroy(); }
    
    Generator(Generator&& other) noexcept : h_(other.h_) { other.h_ = {}; }
    Generator& operator=(Generator&& other) noexcept {
        if (this != &other) {
            if (h_) h_.destroy();
            h_ = other.h_;
            other.h_ = {};
        }
        return *this;
    }
    
    struct iterator {
        handle_type h_;
        
        iterator& operator++() { h_.resume(); return *this; }
        bool operator==(std::default_sentinel_t) const {
            return !h_ || h_.done();
        }
        T const& operator*() const { return h_.promise().current_value; }
    };
    
    iterator begin() {
        if (h_) h_.resume();
        return iterator{h_};
    }
    std::default_sentinel_t end() { return {}; }
};

// Data record structure
struct DataRecord {
    int id;
    std::string content;
    double value;
    
    DataRecord(int i, std::string c, double v)
        : id(i), content(std::move(c)), value(v) {}
};

// 1. Data source generator (lazy evaluation)
Generator<DataRecord> read_data_source(const std::string& source) {
    std::cout << "[Generator] Starting to read from: " << source << "\n";
    
    // Simulate reading data lazily
    for (int i = 1; i <= 10; ++i) {
        // Simulate I/O delay
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        
        std::cout << "[Generator] Yielding record " << i << "\n";
        co_yield DataRecord{i, "Data_" + std::to_string(i), i * 10.5};
    }
    
    std::cout << "[Generator] Finished reading from: " << source << "\n";
}

// 2. Filter generator (lazy filtering)
template<typename Gen, typename Pred>
Generator<DataRecord> filter(Gen& gen, Pred predicate) {
    std::cout << "[Filter] Starting filter\n";
    
    for (auto& record : gen) {
        if (predicate(record)) {
            std::cout << "[Filter] Passing record " << record.id << "\n";
            co_yield record;
        } else {
            std::cout << "[Filter] Filtering out record " << record.id << "\n";
        }
    }
    
    std::cout << "[Filter] Finished filtering\n";
}

// 3. Transform generator (lazy transformation)
template<typename Gen, typename Func>
auto transform(Gen& gen, Func func) -> Generator<decltype(func(std::declval<DataRecord>()))> {
    std::cout << "[Transform] Starting transform\n";
    
    for (auto& record : gen) {
        std::cout << "[Transform] Transforming record " << record.id << "\n";
        co_yield func(record);
    }
    
    std::cout << "[Transform] Finished transforming\n";
}

// 4. Batching generator (accumulate N items)
Generator<std::vector<DataRecord>> batch(Generator<DataRecord>& gen, size_t batch_size) {
    std::cout << "[Batch] Starting batching (size=" << batch_size << ")\n";
    
    std::vector<DataRecord> current_batch;
    current_batch.reserve(batch_size);
    
    for (auto& record : gen) {
        current_batch.push_back(record);
        
        if (current_batch.size() >= batch_size) {
            std::cout << "[Batch] Yielding batch of " << current_batch.size() << "\n";
            co_yield std::move(current_batch);
            current_batch.clear();
            current_batch.reserve(batch_size);
        }
    }
    
    // Yield remaining items
    if (!current_batch.empty()) {
        std::cout << "[Batch] Yielding final batch of " << current_batch.size() << "\n";
        co_yield std::move(current_batch);
    }
    
    std::cout << "[Batch] Finished batching\n";
}

// 5. Take generator (limit number of items)
Generator<DataRecord> take(Generator<DataRecord>& gen, size_t count) {
    std::cout << "[Take] Taking first " << count << " items\n";
    
    size_t taken = 0;
    for (auto& record : gen) {
        if (taken >= count) break;
        
        std::cout << "[Take] Passing record " << record.id << "\n";
        co_yield record;
        taken++;
    }
    
    std::cout << "[Take] Finished taking\n";
}

// 6. Async processing simulation
struct AsyncResult {
    bool success;
    std::string message;
};

Generator<AsyncResult> async_process_batch(const std::vector<DataRecord>& batch) {
    std::cout << "[Async] Processing batch of " << batch.size() << " records\n";
    
    for (const auto& record : batch) {
        // Simulate async processing
        std::this_thread::sleep_for(std::chrono::milliseconds(30));
        
        bool success = (record.id % 3) != 0;  // Simulate failures
        
        AsyncResult result{
            success,
            success ? "Processed ID " + std::to_string(record.id)
                    : "Failed to process ID " + std::to_string(record.id)
        };
        
        co_yield result;
    }
}

// 7. Statistics accumulator using generator
struct Statistics {
    int total_records = 0;
    int successful = 0;
    int failed = 0;
    double sum_values = 0.0;
    
    void print() const {
        std::cout << "\n=== Statistics ===\n";
        std::cout << "Total records: " << total_records << "\n";
        std::cout << "Successful: " << successful << "\n";
        std::cout << "Failed: " << failed << "\n";
        if (total_records > 0) {
            std::cout << "Success rate: " 
                      << (100.0 * successful / total_records) << "%\n";
            std::cout << "Average value: " << (sum_values / total_records) << "\n";
        }
    }
};

// 8. Infinite sequence generator
Generator<int> infinite_sequence(int start = 0, int step = 1) {
    int current = start;
    while (true) {
        co_yield current;
        current += step;
    }
}

// 9. Fibonacci generator
Generator<uint64_t> fibonacci() {
    uint64_t a = 0, b = 1;
    co_yield a;
    co_yield b;
    
    while (true) {
        uint64_t next = a + b;
        co_yield next;
        a = b;
        b = next;
    }
}

// Main processing pipeline
void run_data_pipeline() {
    std::cout << "=== Data Processing Pipeline ===\n\n";
    
    Statistics stats;
    
    // Create pipeline: Read -> Filter -> Transform -> Batch -> Process
    auto data_source = read_data_source("database");
    
    // Filter: Only records with even IDs
    auto filtered = filter(data_source, [](const DataRecord& r) {
        return r.id % 2 == 0;
    });
    
    // Transform: Double the values
    auto transformed = transform(filtered, [](DataRecord r) {
        r.value *= 2.0;
        return r;
    });
    
    // Batch into groups of 2
    auto batched = batch(transformed, 2);
    
    // Process each batch
    std::cout << "\n--- Processing Batches ---\n";
    for (auto& current_batch : batched) {
        stats.total_records += current_batch.size();
        
        // Calculate sum before async processing
        for (const auto& record : current_batch) {
            stats.sum_values += record.value;
        }
        
        // Async process batch
        auto results = async_process_batch(current_batch);
        
        for (const auto& result : results) {
            if (result.success) {
                stats.successful++;
                std::cout << "  ✓ " << result.message << "\n";
            } else {
                stats.failed++;
                std::cout << "  ✗ " << result.message << "\n";
            }
        }
    }
    
    stats.print();
}

// Demonstrate lazy evaluation
void demonstrate_lazy_evaluation() {
    std::cout << "\n\n=== Lazy Evaluation Demo ===\n\n";
    
    // Create infinite sequence
    auto numbers = infinite_sequence(0, 1);
    
    // Take only first 5
    auto limited = take(numbers, 5);
    
    std::cout << "First 5 numbers: ";
    for (int n : limited) {
        std::cout << n << " ";
    }
    std::cout << "\n";
    
    // Fibonacci sequence
    std::cout << "\nFirst 10 Fibonacci numbers: ";
    auto fib = fibonacci();
    int count = 0;
    for (auto n : fib) {
        std::cout << n << " ";
        if (++count >= 10) break;
    }
    std::cout << "\n";
}

// Demonstrate composable generators
void demonstrate_composition() {
    std::cout << "\n\n=== Generator Composition ===\n\n";
    
    // Create a simple data source
    auto source = read_data_source("source");
    
    // Compose multiple operations
    auto filtered = filter(source, [](const DataRecord& r) {
        return r.value > 50.0;
    });
    
    auto limited = take(filtered, 3);
    
    std::cout << "Filtered and limited results:\n";
    for (const auto& record : limited) {
        std::cout << "  ID: " << record.id 
                  << ", Value: " << record.value << "\n";
    }
}

int main() {
    // Run full data pipeline
    run_data_pipeline();
    
    // Demonstrate lazy evaluation
    demonstrate_lazy_evaluation();
    
    // Demonstrate composition
    demonstrate_composition();
    
    std::cout << "\n=== Pipeline Complete ===\n";
    
    return 0;
}

Concepts Demonstrated:

This example shows how coroutines enable elegant data processing pipelines!


Next Steps


Part 22 of 22 - Coroutines (C++20)

Summary

Coroutines are a powerful C++20 feature for:

Start with simple generators, then move to async tasks!


🎉 Congratulations!

You’ve completed the comprehensive C++ STL tutorial covering all 22 chapters with practical, working examples that integrate the concepts from each chapter!

Keep coding and exploring the power of modern C++! 🚀