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 This Library Does

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

  • Buffer sequences — taken straight from Asio and improved

  • Stream conceptsReadStream, WriteStream, ReadSource, WriteSink, BufferSource, BufferSink

  • 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, mock sources/sinks, 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

All of these are built on Capy. Understanding its concepts—tasks, buffer sequences, streams, executors—unlocks the full power of the stack.

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 capy.

Code Convention

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

#include <boost/capy.hpp>
using namespace boost::capy;

Quick Example

This example demonstrates a minimal coroutine that reads from a stream and echoes the data back:

#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(mutable_buffer(buf));
        if(ec.failed())
            co_return;
        auto [wec, wn] = co_await write(stream, const_buffer(buf, n));
        if(wec.failed())
            co_return;
    }
}

int main()
{
    thread_pool pool;
    // In a real application, you would obtain a stream from Corosio
    // and call: run_async(pool.get_executor())(echo(stream));
    return 0;
}

The echo function accepts an any_stream&—a type-erased wrapper that works with any concrete stream implementation. The function reads data into a buffer, then writes it back. Both operations use co_await to suspend 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 launched with run_async.

Next Steps