Capy

Capy abstracts away sockets, files, and asynchrony with type-erased streams and buffer sequences—code compiles fast because the implementation is hidden. It provides the framework for concurrent algorithms that transact in buffers of memory: networking, serial ports, console, timers, and any platform I/O. This is only possible because Capy is coroutine-only, enabling optimizations and ergonomics that hybrid approaches must sacrifice.

What Capy Is

Capy is two things at once:

  • A protocol. IoAwaitable is a protocol for propagating a coroutine’s execution environment—its executor, stop token, and allocator—forward through co_await chains. This is the vocabulary that lets awaitable-based coroutine libraries interoperate.

  • A reference implementation. A concrete library—thread pool, task types, byte streams, buffer sequences, synchronization primitives—that proves the protocol works in practice.

The protocol is the smaller, more general library living inside Capy. Without a shared protocol, N coroutine libraries need N×(N−1) adapters to interoperate. With one shared protocol for environment propagation, a single bridge covers everyone.

The core invariant: a coroutine always resumes on the executor it was started with.

Start a coroutine on a strand, and every resumption—after every co_await—happens on that strand. Shared state touched between suspension points is free of data races without a mutex. A plain awaitable can resume a coroutine on any thread and would break this guarantee. Capy therefore rejects it at compile time, and provides an explicit way to bridge such awaitables when you need one.

See the same-executor invariant for the rationale and bridging a foreign awaitable for the escape hatch.

What Capy Is Not

Capy is not an all-purpose coroutine framework, and it is not an implementation detail of Corosio. It is the execution model and byte-stream layer. It works standalone for logic that operates on streams without any platform I/O (HTTP parsing, protocol state machines, serialization). It also serves as the foundation for Corosio’s networking layer. CERN’s traccc project uses Capy without Corosio for GPU reconstruction pipelines; the Boost.HTTP parser is built entirely on Capy’s byte streams.

What This Library Does

  • Lazy coroutine taskstask<T> with forward-propagating stop tokens and automatic cancellation

  • Buffer sequences — taken straight from Asio and improved

  • Stream concepts — three coroutine stream concepts: ReadStream, WriteStream, Stream

  • Type-erased streamsany_stream, any_read_stream, any_write_stream for fast compilation

  • Concurrency facilities — executors, strands, thread pools, when_all, when_any

  • Test utilities — mock streams, error injection

What This Library Does Not Do

  • Networking — no sockets, acceptors, or DNS; that’s what Corosio provides

  • Protocols — no HTTP, WebSocket, or TLS; see the Http and Beast2 libraries

  • Platform event loops — no io_uring, IOCP, epoll, or kqueue; Capy is the layer above

  • Callbacks or futures — coroutine-only means no other continuation styles

  • Sender/receiver — Capy uses the IoAwaitable protocol, not std::execution

Target Audience

  • Users of Corosio — portable coroutine networking

  • Users of Http — sans-I/O HTTP/1.1 clients and servers

  • Users of Websocket — sans-I/O WebSocket

  • Users of Beast2 — high-level HTTP/WebSocket servers

  • Users of Burl — high-level HTTP client

The Library Family

Capy is the foundation of a family of coroutine libraries. Each builds on Capy’s execution model and byte streams to add a layer of the networking stack. The sibling libraries are in active development; their repositories may be incomplete or not yet released.

  • Capy — execution model, buffer sequences, and byte streams (this library)

  • Corosio — portable coroutine networking (in development)

  • Http — sans-I/O HTTP/1.1 clients and servers (in development)

  • Websocket — sans-I/O WebSocket (in development)

  • Beast2 — high-level HTTP/WebSocket servers (in development)

  • Burl — high-level HTTP client (in development)

Design Philosophy

  • Use case first. Buffer sequences, stream concepts, executor affinity—these exist because I/O code needs them, not because they’re theoretically elegant.

  • Coroutines-only. No callbacks, futures, or sender/receiver. Hybrid support forces compromises; full commitment unlocks optimizations that adapted models cannot achieve.

  • Address the complaints of C++. Type erasure at boundaries, minimal dependencies, and hidden implementations keep builds fast and templates manageable.

Requirements

Assumed Knowledge

  • C++20 coroutines, concepts, and ranges

  • Basic concurrent programming

Compiler Support

  • GCC 12+

  • Clang 17+

  • Apple-Clang (macOS 14+)

  • MSVC 14.34+

  • MinGW

Dependencies

None. Capy is self-contained and does not require Boost.

Linking

Capy is a compiled library. Link against Boost::capy.

Code Convention

Unless otherwise specified, all code examples in this documentation assume the following:

#include <boost/capy.hpp>

using namespace boost::capy;

The examples deliberately leave some results and bindings unused, so the surrounding prose can explain them. Do not build them with -Wall or -Werror.

Quick Example

#include <boost/capy.hpp>

using namespace boost::capy;

task<> echo(any_stream& stream)
{
    char buf[1024];
    for(;;)
    {
        auto [ec, n] = co_await stream.read_some(make_buffer(buf));

        auto [wec, wn] = co_await write(stream, const_buffer(buf, n));

        if(ec)
            co_return;

        if(wec)
            co_return;
    }
}

int main()
{
    // In a real application, you would obtain a stream from Corosio,
    // then start the coroutine on its io_context and run it:
    //
    //   corosio::io_context ioc;
    //   corosio::tcp_socket stream = /* from an acceptor or connect */;
    //   run_async(ioc.get_executor())(echo(stream));
    //   ioc.run();
}

The echo function accepts an any_stream&—a type-erased wrapper that works with any concrete stream implementation. Each co_await suspends until the I/O completes.

The task<> return type (equivalent to task<void>) creates a lazy coroutine that does not start executing until awaited or started with run_async.

Next Steps