Coroutines (C++20)

Cooperative multitasking with suspendable functions

What Are Coroutines?

Coroutines are functions that can suspend execution and resume later, enabling cooperative multitasking without threads.

// Three coroutine keywords:
// co_await - suspend execution, wait for completion
// co_yield - produce a value and suspend
// co_return - return from coroutine

// Example: Simple generator (C++23 std::generator simplifies this)
std::generator<int> count_to(int n) {
    for (int i = 0; i < n; ++i) {
        co_yield i;  // Produce value, suspend
    }
    // Implicit co_return at end
}

Coroutine Components

Promise Type

Defines coroutine behavior: initial/final suspend points, return value handling, exception handling, and yield behavior.

Coroutine Handle

Low-level control over coroutine execution: resume, destroy, check done.

Awaitable

Objects that can be awaited with co_await. Define when to suspend/resume.

Return Object

What the caller receives when invoking a coroutine (e.g., std::generator, Task).

C++23 std::generator

C++23 provides std::generator, eliminating the need for custom promise types in most cases.

#include <generator>
#include <ranges>

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

// Works with ranges
void use_generator() {
    for (int n : fibonacci() | std::views::take(10)) {
        std::cout << n << " ";
    }
    // Output: 0 1 1 2 3 5 8 13 21 34
}

// Recursive generators
std::generator<int> tree_traversal(Node* root) {
    if (!root) co_return;
    
    for (int val : tree_traversal(root->left)) {
        co_yield val;
    }
    co_yield root->value;
    for (int val : tree_traversal(root->right)) {
        co_yield val;
    }
}

When to Use Coroutines

  • Lazy sequences (infinite or large data sets)
  • Asynchronous I/O operations
  • Event-driven programming (state machines)
  • Cooperative multitasking
  • Simple iterations (use regular loops)
  • Parallel computation (use threads)

Best Practices

  • Use C++23 std::generator for sequence generation
  • Handle coroutine lifetimes carefully (don't leak)
  • Mark noexcept when coroutine doesn't throw
  • Don't mix coroutines with traditional callback patterns without care