with_awaitable_senders is intended to be used as a base class of a coroutine promise type to make senders awaitable in coroutines of that type. E.g.,
struct my_task
{
struct promise_type
: std::execution::with_awaitable_senders<promise_type>
{
with_awaitable_senders gives my_task::promise_type an await_transform member function that turns senders into awaitables by calling the as_awaitable customization point. in [exec.with.awaitable.senders], await_transform(value) is specified as follows:
return as_awaitable(std::forward<Value>(value), static_cast<Promise&>(*this));
this does, indeed, make senders awaitable for my_task, but it doesn't guard against senders that change the current execution context. so for example, co_await sndr will cause the coroutine to resume on whatever context sndr completed on, which could be different from the context at suspension.
this is in contrast to what is done for std::execution::task, whose promise's await_transform is specified to return:
returns as_awaitable(affine(std::forward<Sender>(sndr)), *this)
the use of the affine algorithm here guarantees that after co_await-ing a sender, the coroutine will resume on the same execution context it suspended on. (this is done because we observed in the field that it caught many programmers by surprise that a coroutine could thread-hop at co_awaits, leading to bugs.)
one fix here is for with_awaitable_senders::await_transform to do what task::promise_type::await_transform is doing: wrapping senders in affine before calling as_awaitable. then task::promise_type can be respecified in terms of with_awaitable_senders<task::promise_type>, at which point task::promise_type would no longer need to provide its own await_transform memfn.
Proposed Resolution
TODO
with_awaitable_sendersis intended to be used as a base class of a coroutine promise type to make senders awaitable in coroutines of that type. E.g.,with_awaitable_sendersgivesmy_task::promise_typeanawait_transformmember function that turns senders into awaitables by calling theas_awaitablecustomization point. in [exec.with.awaitable.senders],await_transform(value)is specified as follows:this does, indeed, make senders awaitable for
my_task, but it doesn't guard against senders that change the current execution context. so for example,co_await sndrwill cause the coroutine to resume on whatever contextsndrcompleted on, which could be different from the context at suspension.this is in contrast to what is done for
std::execution::task, whose promise'sawait_transformis specified to return:returns as_awaitable(affine(std::forward<Sender>(sndr)), *this)the use of the
affinealgorithm here guarantees that afterco_await-ing a sender, the coroutine will resume on the same execution context it suspended on. (this is done because we observed in the field that it caught many programmers by surprise that a coroutine could thread-hop atco_awaits, leading to bugs.)one fix here is for
with_awaitable_senders::await_transformto do whattask::promise_type::await_transformis doing: wrapping senders inaffinebefore callingas_awaitable. thentask::promise_typecan be respecified in terms ofwith_awaitable_senders<task::promise_type>, at which pointtask::promise_typewould no longer need to provide its ownawait_transformmemfn.Proposed Resolution
TODO