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<int> 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 <typename T>
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<promise_type>::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<promise_type>;
    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
"); return true;
            case CONNECTED: std::printf("await_ready: already connected, no suspend
"); return true;
            default: std::printf("await_ready: status %d, suspend
", tcp_client_.status()); return false;
        }
    }
    bool await_suspend(std::coroutine_handle<> awaiting) {
        std::printf("await_suspend invoked.
");
        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
", ret);
        return ret;
    }
};

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

coro_ret<int> 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
", connect_ret);
    if (connect_ret) {
        printf("connect failed, coroutine return
");
        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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AsynccoroutinePromiseco_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

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.