Capy
Capy is a compiled C++20 library providing abstractions, vocabulary types, and idioms necessary to write programs with event-driven control flows — such as I/O — in a way that makes them efficient and manageable: structured and hard to get wrong.
Coroutines are used to represent the control flow. The program logic is described in a sequential way, familiar to programmers, even though there are tasks being pushed to task queues behind the scenes.
I/O operations are represented via the concept of a stream which consumes and populates buffer sequences. You also get type-erased wrappers for streams, so that programs can compile quickly, without triggering excessive template instantiations.
It offers a number of task synchronization mechanisms, ranging from low-level, like async mutexes and events,
to high-level, like strands, when_all/when_any "joins", and the resume-on-the-same-executor guarantee.
Libraries that want to provide concrete I/O implementations — like those based on epoll or io_uring — can plug into Capy’s system via the IoAwaitable protocol, which describes how tasks can be scheduled,
cancelled, and allocated in memory.
You also get the testing tools that allow you to test your asynchronous flows in a deterministic way.
What Capy is Not
While Capy offers algorithms and interfaces for dealing with I/O,
it does not itself provide any I/O backend (like a wrapper over epoll or io_uring).
For that, you will need to use Capy in tandem with a Capy-conformant library.
This can be either your home-grown one or Corosio.
Quick Example
#include <boost/capy.hpp>
namespace capy = boost::capy;
capy::task<void> echo(capy::any_stream& stream) (1)
{
char buf[1024];
for(;;)
{
auto [ec, n] = co_await stream.read_some(capy::make_buffer(buf)); (2)
auto [wec, _] = co_await capy::write(stream, capy::const_buffer(buf, n));
if(wec)
throw std::system_error(wec); (3)
if(ec == capy::cond::eof) (4)
co_return;
if(ec)
throw std::system_error(ec);
}
}
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 */;
// capy::run_async(ioc.get_executor())(echo(stream));
// ioc.run();
}
| 1 | The task<> return type defines a coroutine that starts suspended. any_stream is a type-erased wrapper that works with any concrete stream implementation. |
| 2 | Each co_await suspends until the I/O operation completes. |
| 3 | You can signal failures by throwing an exception, or by returning a std::error_code. |
| 4 | The condition cond::eof indicates reaching the end of the stream. |
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