Re: Capy Review

Vinnie Falco via Boost <[email protected]> Thu, 25 Jun 2026 01:23:38 -0700
Newsgroups gmane.comp.lib.boost.devel
Message-ID <CA+EzHGc1DbJptqm4bez5oL03504P6-p2zzddAxr0sXOTAwRWcw@mail.gmail.com>
On Thu, Jun 25, 2026 at 12:32 AM Rainer Deyke via Boost <
[email protected]> wrote:

> > Add a member to your awaitable of type continuation and set the coroutine
>   handle. Then pass it to dispatch.
>
> So I can just leave the 'next' member as nullptr, and everything works? That's
> not clear from the documentation at all.  That would have been helpful to
> know three weeks ago.
>

Right.


> I know it's deliberate.  That doesn't make it any less restrictive.
>

Capy is being evaluated here by itself, outside the context of Corosio, and
for a use-case it doesn't claim.

By itself, Capy is quite capable (hence the name). It offers byte-oriented
I/O on optionally type-erased streams, and quite a lot of business logic
can be implemented with this. For example, this library is entirely
implemented with Capy (not Corosio). Here is the HTTP parser:

https://github.com/cppalliance/http/blob/571adcc96ad1f2df935ebbfdf9cb76a20b78608d/include/boost/http/parser.hpp#L410

This function operates on an asynchronous (or synchronous) byte-oriented
stream. It does not require Corosio. No sockets, no platform-specific APIs.
That is why Capy is separate (as previously stated). This is also the
pattern that Beast2 will use. Most business logic implemented on
type-erased streams which have nothing to do with sockets, pure Capy.

Capy and Corosio are a single submission split into two physical libraries.
This review evaluated one half as a standalone general-purpose coroutine
library and found it too restrictive for a use case it does not claim.


> I like my approach better, although I don't know how viable the to_io_awaitable
> function is.  Or why Capy doesn't provide it, if it is.
>

Capy doesn't provide to_io_awaitable() because it can't be implemented
correctly for arbitrary awaitables. The wrapper would have to call resume()
on the right executor in order to maintain the execution guarantees, and it
can't know which executor that is without domain knowledge about how the
foreign operation completes. That domain knowledge is what the author of an
IoAwaitable provides. You write a "leaf awaitable." This is explained in
Section 3.3:

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p4172r1.pdf

The compiler error is the principled choice.


> Awaiting an awaitable is an implementation detail of the function 'f' here.
> It should have no effect on the function signature.


Yes, we agree. The leaf IoAwaitable is the answer here (described above).


> ...this looks a lot like the chain of callbacks that coroutines were
> invented to avoid.
>

`await_sender` usage (from the bridge paper):

    auto result = co_await await_sender(
        ex::on(gpu_sched, ex::then(ex::just(data), classify)));


> >> ..innocent-looking functions like the following do not work correctly:
>

This is not quite right. Your `task` coroutine frame is allocated using the
default allocator. Suboptimal, but it is what you asked for. The launch
function works fine. The coroutine executes. The coroutine frame is
deallocated properly. The invariants are preserved.

...I am allowed to prefer TooManyCooks' approach, and I am allowed to say
> so in my review.
>

And I would point out that TMC doesn't have this problem, because TMC
doesn't have the feature. TooManyCooks does not support customizing the
frame allocator. It uses global ::operator new unconditionally, and the
guidance is to "link with tcmalloc, mimalloc, or jemalloc instead of the
default glibc malloc." If you don't have one of these the CMakeLists.txt
will even warn you.

The two-phase syntax is the price of a feature TooManyCooks doesn't offer.

Capy follows in Boost's original tradition of pioneering solutions.


> I lose the ability to get a result back to the coroutine, but I was never
> going to be able to do anything useful with that result anyway because
> boost::capy::run_async swallows return values.  If I want to use the
> result locally instead of returning it, I can do so within the callback.
>

I'm getting a little lost because you are first talking about capy::run(),
then capy::run_async(). The two functions serve different purposes.

capy::run_async() is "fire and forget". The caller indicates they are
disinterested in the result. If they were interested in the result, then
they would be expressed as a coroutine, which uses co_await, and either
awaits the task directly or awaits a call to capy::run().

Please help me understand what the actual issue is.

Thanks
_______________________________________________
Boost mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://lists.boost.org/mailman3/lists/boost.lists.boost.org/
Archived at: https://lists.boost.org/archives/list/[email protected]/message/F2IJBJ5FUC3PYT5RBOW75GBDFYYNMIBI/