Boost.Capy

Boost.Capy is a lightweight C++20 coroutine framework that provides lazy tasks with automatic executor affinity propagation.

What This Library Does

Capy solves a specific problem: when you co_await a child coroutine, where does it resume? Without affinity tracking, completions can arrive on arbitrary threads, forcing you to add synchronization everywhere.

Capy provides:

  • Lazy tasks that do not start until awaited or explicitly launched

  • Automatic affinity propagation through coroutine call chains

  • Zero-overhead dispatcher protocol for custom awaitables

  • Frame allocation recycling to minimize allocation overhead

What This Library Does Not Do

Capy is not a general-purpose I/O framework. It does not include:

  • Event loops or I/O polling (use Asio, io_uring wrappers, etc.)

  • Networking primitives (sockets, HTTP, etc.)

  • The sender/receiver execution model (P2300)

Capy integrates with existing I/O frameworks by wrapping their completion mechanisms in affine-aware awaitables.

Design Philosophy

Lazy by default. Tasks suspend immediately on creation. This enables structured composition where parent coroutines naturally await their children. Eager execution is available through async_run.

Affinity through the protocol. The dispatcher propagates through await_suspend parameters, not through thread-local storage or global state. This makes the data flow explicit and testable.

Type erasure at boundaries. Tasks use type-erased dispatchers (any_dispatcher) internally, paying the indirection cost once rather than templating everything. For I/O-bound code, this cost is negligible.

Requirements

  • C++20 compiler with coroutine support

  • Boost (for system::error_code, core::string_view)

Tested Compilers

  • GCC 11+

  • Clang 14+

  • MSVC 19.29+ (Visual Studio 2019 16.10+)

Quick Example

#include <boost/capy/task.hpp>
#include <boost/capy/async_run.hpp>
#include <iostream>

using boost::capy::task;
using boost::capy::async_run;

task<int> compute()
{
    co_return 42;
}

task<void> run(auto executor)
{
    int result = co_await compute();
    std::cout << "Result: " << result << "\n";
}

int main()
{
    io_context ioc;
    async_run(ioc.get_executor())(run(ioc.get_executor()));
    ioc.run();
}

Next Steps

  • Quick Start — Get a working program in 5 minutes

  • Tasks — Understand lazy coroutines

  • Executors — Learn about the execution model