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 tasks —
task<T>with forward-propagating stop tokens and automatic cancellation -
Buffer sequences — taken straight from Asio and improved
-
Stream concepts —
ReadStream,WriteStream,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.
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(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
-
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 six stream concepts