Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Modern C++20/23

Coroutines Basics (C++20)

Coroutines: Suspendable Functions

C++20 coroutines are functions that can suspend execution and resume later, enabling asynchronous programming and lazy generators without callbacks.

Coroutine Keywords

A function is a coroutine if it contains any of:

  • co_await: Suspend until expression completes
  • co_yield: Suspend and produce a value
  • co_return: Complete and optionally return a value
generator<int> counter() {
    for (int i = 0; i < 5; ++i) {
        co_yield i;  // Suspend and produce value
    }
}

Basic Generator Example

Simple generator coroutine:

#include <coroutine>
#include <iostream>

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 = value;
            return {};
        }
        
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
    
    std::coroutine_handle<promise_type> coro;
    
    generator(std::coroutine_handle<promise_type> h) : coro(h) {}
    ~generator() { if (coro) coro.destroy(); }
    
    bool next() {
        coro.resume();
        return !coro.done();
    }
    
    T value() {
        return coro.promise().current_value;
    }
};

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

// Usage:
auto gen = range(0, 5);
while (gen.next()) {
    std::cout << gen.value() << '\\n';
}

Coroutine Mechanics

Promise Type

Every coroutine requires a promise_type that defines behavior:

struct promise_type {
    // Called to create the coroutine's return object
    ReturnType get_return_object();
    
    // Should coroutine suspend before executing body?
    auto initial_suspend();  // Returns suspend_always or suspend_never
    
    // Should coroutine suspend after completing?
    auto final_suspend() noexcept;
    
    // Handle co_yield value
    auto yield_value(T value);
    
    // Handle co_return
    void return_void();
    // OR
    void return_value(T value);
    
    // Exception handling
    void unhandled_exception();
};

Coroutine Handle

Manages coroutine state:

std::coroutine_handle<PromiseType> handle;

handle.resume();   // Resume coroutine execution
handle.done();     // Check if coroutine completed
handle.destroy();  // Destroy coroutine state
handle.promise();  // Access promise object

co_await Expression

Suspend until awaitable completes:

Task async_operation() {
    auto result = co_await some_async_call();
    // Resumed when async_call completes
    co_return result;
}

Awaitable Type

struct awaitable {
    bool await_ready();  // Can we skip suspension?
    void await_suspend(std::coroutine_handle<>);  // Called when suspending
    T await_resume();    // Called when resuming, returns value
};

Infinite Generator

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

auto fib = fibonacci();
for (int i = 0; i < 10; ++i) {
    fib.next();
    std::cout << fib.value() << ' ';
}
// 0 1 1 2 3 5 8 13 21 34

Lazy Evaluation

Coroutines enable lazy evaluation—values computed on demand:

generator<int> lazy_transform(const std::vector<int>& vec) {
    for (int x : vec) {
        co_yield x * x;  // Computed only when requested
    }
}

Async Task Pattern

struct Task {
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

Task async_work() {
    co_await some_operation();
    co_await another_operation();
    co_return;
}

Range-Based for with Coroutines

Make generator iterable:

template<typename T>
struct generator {
    // ... previous code ...
    
    struct iterator {
        std::coroutine_handle<promise_type> coro;
        
        iterator& operator++() {
            coro.resume();
            return *this;
        }
        
        bool operator!=(const iterator&) const {
            return !coro.done();
        }
        
        T operator*() const {
            return coro.promise().current_value;
        }
    };
    
    iterator begin() {
        coro.resume();
        return {coro};
    }
    
    iterator end() {
        return {nullptr};
    }
};

// Now works with range-based for:
for (int x : range(0, 5)) {
    std::cout << x << ' ';
}

Coroutine State

Coroutine state is heap-allocated (can be optimized away):

// State includes:
// - Parameters
// - Local variables
// - Promise object
// - Suspension point information

Compared to Traditional Approaches

ApproachCallbacksState MachinesCoroutines
ReadabilityPoorMediumExcellent
ComplexityHighHighLow
Error HandlingDifficultComplexNatural
PerformanceFastFastFast
Conceptual Check

What makes a function a coroutine?

Runtime Environment

Interactive Lab

1#include <iostream>
2 
3// Simplified generator for demonstration
4struct SimpleGen {
5 int current = 0;
6 int end;
7
8 SimpleGen(int e) : end(e) {}
9
10 bool next() {
11 if (current < end) {
12 ++current;
13 return true;
14 }
15 return false;
16 }
17
18 int value() const { return current - 1; }
19};
20 
21// Simulates generator behavior without actual coroutines
22SimpleGen simulate_range(int start, int end) {
23 return SimpleGen(end - start);
24}
25 
26int main() {
27 std::cout << "Simulated coroutine generator:\n";
28 auto gen = simulate_range(0, 5);
29
30 while (gen.next()) {
31 std::cout << gen.value() << ' ';
32 }
33 std::cout << '\n';
34
35 return 0;
36}
System Console

Waiting for signal...