On Thu, 5 Feb 2026 at 18:48, Vinnie Falco <vinnie.falco@gmail.com> wrote:
On Wed, Feb 4, 2026 at 2:26 PM Ruben Perez <rubenperez038@gmail.com> wrote:
Can I implement an Asio universal async operation using Capy?
We had a conversation today in the Official C++ Language Slack Workspace and this clarified my understanding of what you are asking for.
You want: asynchronous initiating functions which have Asio's Universal Asynchronous Model [1] which drive Capy's type-erased streams. This allows you to implement business logic (for example, a PostgresSQL client implementation) as "sans-I/O." That is, agnostic to the model of asynchrony. While allowing existing users who are already Asio-based, to interact with your algorithms - this provides a clear migration path. Otherwise known as "brownfield development."
Yes this is possible. The example that I provided previously, offers Capy's execution model wrapped around an invisible Asio socket. I have added a new example which wraps Asio's universal asynchronous model around a type-erased Capy stream:
* `uni_stream` models Asio's AsyncReadStream and AsyncWriteStream and uses completion tokens:
https://github.com/cppalliance/capy/blob/4c4fa32cb96865eda059d1cb559f928624c...
This does not handle cancellation. I don't think that trying to write Asio universal async operations using Capy is a good idea. I would encourage you to remove the "sans-io" term from Capy, since it assumes a particular I/O model.
Most of your algorithms can be expressed in terms of capy::any_stream, but there are some things which cannot. Resolving DNS names, establishing outgoing connections, or accepting incoming connections are not operations reflected by Capy concepts. Your uni_postgres_client would need to have some pure virtual member functions to do these things (they can be capy::task coroutines of course), and a derived class would have to implement some I/O specific things. If your underlying stream is asio socket then you would write asio code to connect or resolve names. If your underlying stream is Corosio, then you would use Corosio's APIs to do those things.
Is there a reason why reading and writing have been placed in Capy, while other operations have been placed in Corosio? Connection establishment is a need for all clients, regardless of what the service they connect to.
Happy to elaborate further if there are specific questions.
If I happen to write a library exposing a capy API like this: class pg_client { public: capy::io_task<void> query(string_view q); }; Let's say I have a user that wants to adopt my library. The user has the following code already working in production: asio::awaitable<void> co_main() { // Some code that uses Asio, MySQL and MQTT5 } int main() { asio::io_context ctx; asio::co_spawn(ctx, co_main(), asio::detached); ctx.run(); } What's the best way for this user to adopt my library? Do they need to port everything in co_main to capy and corosio? Or can they keep it and use my library?