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.
IoAwaitableis a protocol for propagating a coroutine’s execution environment—its executor, stop token, and allocator—forward throughco_awaitchains. 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 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 tasks —
task<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 streams —
any_stream,any_read_stream,any_write_streamfor 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.
Code Convention
|
Unless otherwise specified, all code examples in this documentation assume the following:
The examples deliberately leave some results and bindings unused, so the
surrounding prose can explain them. Do not build them with |
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.
Next Steps
-
Quick Start — Set up your first Capy project
-
C++20 Coroutines Tutorial — Learn coroutines from the ground up
-
Concurrency Tutorial — Understand threads, mutexes, and synchronization
-
Coroutines in Capy — Deep dive into
task<T>and the IoAwaitable protocol -
Buffer Sequences — Buffer types, sequences, system I/O, and the algorithms over them
-
Stream Concepts — Understand the three stream concepts