MCPInstall

cpp-coroutines

C++20 coroutines skill for understanding coroutine mechanics and debugging. Use when working with coawait, coyield, coreturn, implementing promisetype, understanding coroutine frame layout, debugging suspended coroutines in GDB, or inspecting frame allocation with Compiler Explorer. Activates on queries about C++20 coroutines, coawait, coyield, promisetype, coroutinehandle, coroutine suspension, or coroutine frame.

Installs
535
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/mohitmishra786/low-level-dev-skills --skill cpp-coroutines
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads cpp-coroutines automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

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

// 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

#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

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

// 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
// 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

# 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

#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)

#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

# Compile with debug info
g++ -std=c++20 -g -O0 -o app app.cpp
gdb ./app
(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:

MitigationEffect
-O2 HALOReduces generated frame glue
Out-of-line co_await in .cppCuts recompilation cascade
Pimpl for coroutine return typesHides awaitable templates from headers
ccache / modulesSee 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

IssueCauseFix
co_await in a non-coroutineFunction missing coroutine return typeChange return type to a coroutine type
Dangling handle after co_returnUsing handle after coroutine finishesCheck handle.done() before resume
Double-resumeResuming an already-resumed coroutineTrack state; only resume when suspended
Coroutine frame never freedForgot handle.destroy()Use RAII wrapper (Task, Generator)
Heap allocation overheadNew frame per coroutine callEnable HALO (Heap Allocation eLision Optimization) with -O2
Recursive co_await depthStack overflow from deep chainsUse 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

FAQ

What does the cpp-coroutines skill do?

C++20 coroutines skill for understanding coroutine mechanics and debugging. Use when working with coawait, coyield, coreturn, implementing promisetype, understanding coroutine frame layout, debugging suspended coroutines in GDB, or inspecting frame allocation with Compiler Explorer. Activates on queries about C++20 coroutines, coawait, coyield, promisetype, coroutinehandle, coroutine suspension, or coroutine frame.

How do I install cpp-coroutines?

Run npx skills add https://github.com/mohitmishra786/low-level-dev-skills --skill cpp-coroutines in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/cpp-coroutines/ (personal) or .claude/skills/cpp-coroutines/ (project).

Which AI agents can use cpp-coroutines?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is cpp-coroutines safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains cpp-coroutines?

It is published by mohitmishra786 in mohitmishra786/low-level-dev-skills. It has 535 recorded installs.

More from mohitmishra786/low-level-dev-skills

View all →

cmake

mohitmishra786/low-level-dev-skills

CMake build system skill for C/C++ projects. Use when writing or refactoring CMakeLists.txt, configuring out-of-source builds, selecting generators (Ninja, Make, VS), managing targets and dependencies with targetlinklibraries, integrating external packages via findpackage or FetchContent, enabling sanitizers, setting up toolchain files for cross-compilation, or exporting CMake packages. Activates on queries about CMakeLists.txt, cmake configure errors, target properties, install rules, CPack, or CMake presets.

static-analysis

mohitmishra786/low-level-dev-skills

Static analysis skill for C/C++ codebases. Use when hardening code quality, triaging noisy builds, running clang-tidy, cppcheck, or scan-build, interpreting check categories, suppressing false positives, or integrating static analysis into CI. Activates on queries about clang-tidy checks, cppcheck, scan-build, compilecommands.json, code hardening, or static analysis warnings.

DevOps & Cloud1.2K installs

gdb

mohitmishra786/low-level-dev-skills

GDB debugger skill for C/C++ programs. Use when starting a GDB session, setting breakpoints, stepping through code, inspecting variables, debugging crashes, using reverse debugging (record/replay), remote debugging with gdbserver, or loading core dumps. Activates on queries about GDB commands, segfaults, hangs, watchpoints, conditional breakpoints, pretty-printers, Python GDB scripting, or multi-threaded debugging.

llvm

mohitmishra786/low-level-dev-skills

LLVM IR and pass pipeline skill. Use when working directly with LLVM Intermediate Representation (IR), running opt passes, generating IR with llc, inspecting or writing LLVM IR for custom passes, or understanding how the LLVM backend lowers IR to assembly. Activates on queries about LLVM IR, opt, llc, llvm-dis, LLVM passes, IR transformations, or building LLVM-based tools.

Backend & APIs901 installs

sanitizers

mohitmishra786/low-level-dev-skills

Compiler sanitizer skill for runtime bug detection in C/C++. Use when enabling and interpreting AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), ThreadSanitizer (TSan), MemorySanitizer (MSan), or LeakSanitizer (LSan) with GCC or Clang. Activates on queries about sanitizer flags, sanitizer reports, ASANOPTIONS, memory errors, data races, undefined behaviour, uninitialised reads, or choosing which sanitizer to use for a given bug class.

core-dumps

mohitmishra786/low-level-dev-skills

Core dump analysis skill for production crash triage. Use when loading core files in GDB or LLDB, enabling core dump generation on Linux/macOS, mapping symbols with debuginfo or debuginfod, or extracting backtraces from crashes without re-running the program. Activates on queries about core files, ulimit, coredumpctl, debuginfod, crash triage, or analyzing segfaults from production binaries.

DevOps & Cloud767 installs

Popular code quality & workflow skills

View category →

grill-me

mattpocock/skills

A relentless interview to sharpen a plan or design.

Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.

handoff

mattpocock/skills

Compact the current conversation into a handoff document for another agent to pick up.

domain-modeling

mattpocock/skills

Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.

codebase-design

mattpocock/skills

Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.

diagnosing-bugs

mattpocock/skills

Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.