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. This is the role IoAwaitable plays.
|
The core invariant: a coroutine always resumes on the executor it was launched with. Launch 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—usable standalone for logic that operates on streams without any platform I/O (HTTP parsing, protocol state machines, serialization), and usable 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 — seven coroutine stream concepts:
ReadStream,WriteStream,Stream,ReadSource,WriteSink,BufferSource,BufferSink -
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, 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.
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:
|
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(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 launch 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. 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
-
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 — Master the concept-driven buffer model
-
Stream Concepts — Understand the seven stream concepts