Skip to main content
skill CX Codex

cpp-coroutines — Claude Code skill: what it does, how to install

Guides coding agents through C++20 coroutine mechanics. Helps build promise types, generate custom awaitables, trim frame allocations, and debug suspended tasks in GDB.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add mohitmishra786/low-level-dev-skills --skill cpp-coroutines -a claude-code
Open on GitHub

What cpp-coroutines does

The skill gives agents concrete patterns for co_await, co_yield, and co_return. It guides the creation of a minimal Task<T> type, custom generator iterators, and custom awaitables with await_ready, await_suspend, and await_resume.

Coroutines allocate a heap frame to hold active variables, the promise_type, and suspension state pointers. The workflow details how to reduce this allocation, such as releasing large vectors before reaching a suspension point instead of keeping them alive across calls.

It also outlines debugging steps in GDB 14+, focusing on inspecting the coroutine_handle and viewing promise contents. Compiling with -std=c++20 is mandatory because these abstractions rely strictly on standard coroutine headers.

What it helps with

  • Implement a minimal Task return type with custom promise_type
  • Write a lazy generator using co_yield and iterators
  • Inspect and debug suspended coroutine state in GDB
  • Reduce heap frame memory footprint across suspension points

Example prompts for the agent

  • Show me how to implement promise_type for a C++20 Task
  • How do I write a generator with co_yield in C++20?
  • How can I inspect a suspended coroutine handle in GDB?

How to install cpp-coroutines

1Skills CLI (Vercel)

npx skills add mohitmishra786/low-level-dev-skills --skill cpp-coroutines -a claude-code

2Manually: put the files in place

~/.claude/skills/cpp-coroutines/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/cpp-coroutines/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about cpp-coroutines

How to install cpp-coroutines in Claude Code?

Run npx skills add mohitmishra786/low-level-dev-skills --skill cpp-coroutines -a claude-code. The tool downloads the skill directly into your Claude Code setup.

What does cpp-coroutines do?

It equips your AI assistant with C++20 coroutine implementations and memory management rules. The guidance covers promise_type, co_await contracts, and frame sizing.

What tools are required for coroutine debugging?

The skill relies on GDB 14 or newer for native coroutine inspection via commands like info coroutines. Code must be built with -std=c++20.

Source text

SKILL.md · MIT
---
name: cpp-coroutines
description: C++20 coroutines skill for understanding coroutine mechanics and debugging. Use when working with co_await, co_yield, co_return, implementing promise_type, understanding coroutine frame layout, debugging suspended coroutines in GDB, or inspecting frame allocation with Compiler Explorer. Activates on queries about C++20 coroutines, co_await, co_yield, promise_type, coroutine_handle, coroutine suspension, or coroutine frame.
---

# C++20 Coroutines

## Purpose

Guide agents through C++20 coroutine mechanics: `co_await`, `co_yield`, `co_return`, implementing the required `promise_type`, understanding coroutine frame memory layout, debugging suspended coroutines in GDB, and reducing frame allocation overhead.

## Triggers

- "How do co_await, co_yield, and co_return work?"
- "How do I implement promise_type for a coroutine?"
- "How does a coroutine suspend and resume?"
- "How do I debug a suspended coroutine in GDB?"
- "How much memory does a coroutine frame use?"
- "How do I write a generator with co_yield?"

## Workflow

### 1. The three coroutine keywords

```cpp
// co_return — return a value and end the coroutine
co_return value;

// co_yield — produce a value, suspend, resume later
co_yield value;

// co_await — suspend until an awaitable completes
auto result = co_await some_awaitable;
```

A function is a coroutine if it contains any of these three keywords. Its return type must be a coroutine type with a `promise_type`.

### 2. Minimal coroutine type — Task

```cpp
#include <coroutine>
#include <stdexcept>
#include <optional>

template <typename T>
struct Task {
    struct promise_type {
        std::optional<T> value;
        std::exception_ptr exception;

        Task get_return_object() {
            return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
        }

        std::suspend_always initial_suspend() { return {}; }  // lazy start
        std::suspend_always final_suspend() noexcept { return {}; }

        void return_value(T v) { value = std::move(v); }

        void unhandled_exception() { exception = std::current_exception(); }
    };

    std::coroutine_handle<promise_type> handle;

    explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {}

    Task(Task&&) = default;
    Task& operator=(Task&&) = default;

    ~Task() { if (handle) handle.destroy(); }

    T get() {
        handle.resume();                      // resume to completion
        if (handle.promise().exception)
            std::rethrow_exception(handle.promise().exception);
        return std::move(*handle.promise().value);
    }
};

// Usage
Task<int> compute() {
    co_return 42;
}

int main() {
    auto task = compute();
    int result = task.get();   // 42
}
```

### 3. Generator with co_yield

```cpp
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 {}; }
        void return_void() {}
        void unhandled_exception() { throw; }

        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};                     // suspend after yielding
        }
    };

    std::coroutine_handle<promise_type> handle;

    explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Generator() { if (handle) handle.destroy(); }

    struct iterator {
        std::coroutine_handle<promise_type> handle;
        bool done;

        iterator& operator++() {
            handle.resume();
            done = handle.done();
            return *this;
        }
        T operator*() const { return handle.promise().current_value; }
        bool operator!=(std::default_sentinel_t) const { return !done; }
    };

    iterator begin() {
        handle.resume();                   // advance to first yield
        return {handle, handle.done()};
    }
    std::default_sentinel_t end() { return {}; }
};

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

for (int x : iota(0, 5)) {
    std::cout << x << ' ';   // 0 1 2 3 4
}
```

### 4. Awaitable — custom co_await target

```cpp
// An awaitable has three methods:
// await_ready() — true means don't suspend
// await_suspend(handle) — suspend: store handle, schedule resume
// await_resume() — return value of co_await expression

struct TimerAwaitable {
    int delay_ms;

    bool await_ready() const noexcept { return delay_ms <= 0; }

    void await_suspend(std::coroutine_handle<> h) {
        // Schedule h.resume() to be called after delay
        std::thread([h, this]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
            h.resume();
        }).detach();
    }

    void await_resume() const noexcept {}  // no return value
};

// suspend_always and suspend_never are built-in awaitables
std::suspend_always{};   // always suspends
std::suspend_never{};    // never suspends (no-op)
```

### 5. Coroutine frame layout and memory

The compiler allocates a coroutine frame (heap object) containing:
- Local variables that live across suspension points
- The promise object
- The current suspension state (where to resume)
- A pointer to the resumption/destruction functions

```cpp
// Inspect frame size with Compiler Explorer (godbolt.org)
// Compile with: g++ -std=c++20 -O2 -S
// Look for: operator new call size in the generated asm
// Or: clang -std=c++20 -O2 -emit-llvm -S | grep "coro.size"

// Reduce frame size:
// 1. Don't keep large objects alive across co_await
struct Bad {
    std::vector<char> large_buf;   // whole vector lives in frame
    co_return large_buf.size();    // large_buf crosses suspension
};

// 2. Move data out before suspending
std::vector<char> buf = get_data();
size_t sz = buf.size();            // capture only what's needed
buf.clear();                       // release before suspension
co_await next_event;
// sz still valid; buf released
```

### 6. Debugging suspended coroutines in GDB

```bash
# Coroutines appear as regular stack frames after resume()
# To inspect a suspended coroutine:

(gdb) info locals
# Look for coroutine_handle variables

# Print the promise object
(gdb) p *(promise_type*)(handle.__handle_)
# GDB 14+ has coroutine-specific support
(gdb) info coroutines        # GCC coroutine support (experimental)

# Step through coroutine execution
(gdb) step     # enters co_await implementation
(gdb) finish   # returns from coroutine frame function
(gdb) next     # step over suspension point

# View all threads (coroutines running on thread pool)
(gdb) info threads
(gdb) thread 2
(gdb) bt
```

### 7. Boost.Asio `co_spawn` and `co_await`

```cpp
#include <boost/asio.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/awaitable.hpp>

namespace net = boost::asio;

net::awaitable<void> echo_session(net::ip::tcp::socket socket) {
    char buf[1024];
    for (;;) {
        std::size_t n = co_await socket.async_read_some(net::buffer(buf));
        co_await net::async_write(socket, net::buffer(buf, n));
    }
}

int main() {
    net::io_context io;
    net::co_spawn(io, listen_accept(io), net::detached);
    io.run();
}
```

`co_spawn` launches coroutines on an executor; `co_await` chains completion tokens without callback nesting.

### 8. `std::generator` (C++23)

```cpp
#include <generator>
#include <ranges>

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

// Usage
for (int v : fibonacci() | std::views::take(10))
    printf("%d\n", v);
```

Lazy sequences without manual coroutine handle management — compiler provides `std::generator` promise type.

### 9. Coroutine frame layout in GDB

```bash
# Compile with debug info
g++ -std=c++20 -g -O0 -o app app.cpp
gdb ./app
```

```gdb
(gdb) break my_coro
(gdb) run
(gdb) info frame                    # current stack frame
(gdb) info locals                   # promise, handle in scope

# Inspect coroutine frame pointer (compiler-specific mangling)
(gdb) p *(MyPromise*)h.address()    # h = coroutine_handle

# GCC coroutine support (GCC 14+)
(gdb) info coroutines

# Pretty-print promise state
(gdb) set print pretty on
(gdb) p promise
```

Suspended coroutines may not appear on stack until resumed — trace via stored `coroutine_handle`.

### 10. Compilation time impact

Coroutines increase template instantiation and header parsing cost:

| Mitigation | Effect |
|------------|--------|
| `-O2` HALO | Reduces generated frame glue |
| Out-of-line `co_await` in `.cpp` | Cuts recompilation cascade |
| Pimpl for coroutine return types | Hides `awaitable` templates from headers |
| `ccache` / modules | See `skills/rust/rust-build-times` patterns for C++ |

Measure with `g++ -ftime-report` or `clang -ftime-trace`. Coroutine-heavy headers (Asio) benefit from unity builds sparingly — balance with RAM use.

### 11. Common pitfalls

| Issue | Cause | Fix |
|-------|-------|-----|
| `co_await` in a non-coroutine | Function missing coroutine return type | Change return type to a coroutine type |
| Dangling handle after `co_return` | Using handle after coroutine finishes | Check `handle.done()` before resume |
| Double-resume | Resuming an already-resumed coroutine | Track state; only resume when suspended |
| Coroutine frame never freed | Forgot `handle.destroy()` | Use RAII wrapper (Task, Generator) |
| Heap allocation overhead | New frame per coroutine call | Enable HALO (Heap Allocation eLision Optimization) with `-O2` |
| Recursive co_await depth | Stack overflow from deep chains | Use `std::coroutine_handle<>` tail-call pattern |

## Related skills

- Use `skills/compilers/cpp-templates` for other advanced C++20 features
- Use `skills/rust/rust-async-internals` for Rust's equivalent Future/Poll model
- Use `skills/debuggers/gdb` for GDB session management

© mohitmishra786 · MIT · mohitmishra786/low-level-dev-skills

Similar skills

All skills for Programming languages →

From the same set79

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor