Fundamentals 13 min read

Understanding C++20 Coroutines: Promise, Await, and Practical Examples

C++20 coroutines turn functions containing co_await, co_yield, or co_return into suspendable tasks, requiring a promise_type that defines get_return_object, initial_suspend, final_suspend, yield_value, return handling, and exception management, while handles manage resumption, and custom awaiters enable asynchronous I/O such as non‑blocking TCP connections.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Understanding C++20 Coroutines: Promise, Await, and Practical Examples

Coroutines are functions that can be suspended and later resumed. In C++20, a function becomes a coroutine when it contains any of co_await , co_yield , or co_return .

A minimal C++20 coroutine example:

coro_ret
number_generator(int begin, int count) {
    std::cout << "number_generator invoked." << std::endl;
    for (int i = begin; i < count; ++i) {
        co_yield i;
    }
    co_return;
}

int main(int argc, char* argv[]) {
    auto g = number_generator(1, 10);
    std::cout << "begin to run!" << std::endl;
    while (!g.resume()) {
        std::cout << "got number:" << g.get() << std::endl;
    }
    std::cout << "coroutine done, return value:" << g.get() << std::endl;
    return 0;
}

The function number_generator contains co_yield and co_return , so it is a coroutine. When execution reaches co_yield i , the coroutine suspends; control returns to the caller until resume() is invoked.

The return type of a coroutine must provide a nested promise_type . The promise_type defines several required interfaces:

get_return_object() – creates the coroutine’s return object.

initial_suspend() – decides whether the coroutine starts suspended ( std::suspend_always ) or runs immediately ( std::suspend_never ).

final_suspend() – runs when the coroutine finishes; returning std::suspend_always requires the caller to destroy the coroutine handle.

yield_value(T) – called on each co_yield , stores the yielded value and usually returns std::suspend_always .

return_void() or return_value(T) – invoked by co_return .

unhandled_exception() – handles exceptions thrown inside the coroutine.

A simplified promise_type implementation used in the article:

template
struct coro_ret {
    struct promise_type {
        std::cout << "promise constructor invoked." << std::endl;
        auto get_return_object() {
            std::cout << "get_return_object invoked." << std::endl;
            return coro_ret{std::coroutine_handle
::from_promise(*this)};
        }
        auto initial_suspend() {
            std::cout << "initial_suspend invoked." << std::endl;
            return std::suspend_always{};
        }
        void return_void() {
            std::cout << "return void invoked." << std::endl;
        }
        auto yield_value(const T& v) {
            std::cout << "yield_value invoked." << std::endl;
            return_data_ = v;
            return std::suspend_always{};
        }
        auto final_suspend() noexcept {
            std::cout << "final_suspend invoked." << std::endl;
            return std::suspend_always{};
        }
        void unhandled_exception() {
            std::cout << "unhandled_exception invoked." << std::endl;
            std::exit(1);
        }
        T return_data_;
    };
    using handle_type = std::coroutine_handle
;
    handle_type coro_handle_;
    bool resume() {
        if (!coro_handle_.done()) coro_handle_.resume();
        return coro_handle_.done();
    }
    T get() const { return coro_handle_.promise().return_data_; }
    ~coro_ret() { if (coro_handle_) coro_handle_.destroy(); }
    coro_ret(handle_type h) : coro_handle_(h) {}
};

The article also explains the relationship between three key objects: the promise , the coroutine handle , and the coroutine state . The handle ( std::coroutine_handle<promise_type> ) provides resume() , done() , and destroy() operations, while the promise mediates data exchange between the coroutine and its caller.

For asynchronous I/O, the article shows how to implement a custom awaiter for a non‑blocking connect operation:

struct connect_awaiter {
    coroutine_tcp_client& tcp_client_;
    bool await_ready() {
        switch (tcp_client_.status()) {
            case ERROR:    std::printf("await_ready: error, no suspend\n"); return true;
            case CONNECTED: std::printf("await_ready: already connected, no suspend\n"); return true;
            default: std::printf("await_ready: status %d, suspend\n", tcp_client_.status()); return false;
        }
    }
    bool await_suspend(std::coroutine_handle<> awaiting) {
        std::printf("await_suspend invoked.\n");
        tcp_client_.handle_ = awaiting;
        return true; // suspend
    }
    int await_resume() {
        int ret = tcp_client_.status() == CONNECTED ? 0 : -1;
        std::printf("await_resume invoked, ret:%d\n", ret);
        return ret;
    }
};

Using this awaiter, a coroutine can perform a TCP connection like:

coro_ret
connect_addr_example(io_service& service, const char* ip, int16_t port) {
    coroutine_tcp_client client;
    auto connect_ret = co_await client.connect(ip, port, 3, service);
    printf("client.connect return:%d\n", connect_ret);
    if (connect_ret) {
        printf("connect failed, coroutine return\n");
        co_return -1;
    }
    do_something_with_connect(client);
    co_return 0;
}

The article concludes that C++20 provides a powerful, highly customizable coroutine mechanism, but the standard library support is still limited. Full library support is expected in C++23.

ProgrammingAsynccoroutinePromiseco_awaitC++20
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.