1. Introduction
Any sender [P2300R9] wishing to support cancellation using cancellation tokens, particularly
, must currently store as a subobject of its operation state a
object containing a reference to the operation state itself:
class my_sender { template < typename R > class op_state { R receiver ; class cb_t { op_state & op ; public : cb_t ( op_state & op ) : op ( op ) {} void operator ()() const { op . on_stop_requested (); } }; std :: inplace_stop_callback < cb_t > cb ; void on_stop_requested (); }; };
This naturally comes with a small but non-zero cost in the space required for each operation state. While standard library implementations could likely eliminate this cost for any standard senders in just the manner described in this paper, that will not be possible for any user senders wishing to make use of
as it is currently specified.
2. Proposal
We propose to introduce a new standard library CRTP class template for use as a base subobject of classes which would otherwise contain as a subobject a
with a reference to the enclosing object:
template < typename T > class inplace_stoppable_base ;
As is usual with CRTP, it is required that the class type
is derived (directly or indirectly) from
and that the conversion from
to
is accessible to
.
Unlike
, the only constructor of
has
access and takes a single parameter of type
.
Upon request for cancellation of the
to which a
object is registered, the member function
is invoked on the derived object of type
.
Using this class template, the previous example can not only be optimised, but vastly simplified:
class my_sender { template < typename R > class op_state : std :: inplace_stoppable_base < op_state < R >> { R receiver ; // invoked upon request for cancellation. void on_stop_requested (); }; };
Note that it is possible to implement
in terms of
, while the reverse is not true.
2.1. Naming of the derived member function
We propose the name
which has much precedent in libraries in the wild and in other languages, but little directly applicable precedent in the standard library. The standard library does include a few things named similarly:
-
Allocator propagation traits such as
.propagate_on_container_move_assignment -
A few functions related to
such asiostream
.set_emit_on_sync
Other possible names include:
-
stop_requested
This is a reasonable name, but
itself has a function with the same name used for a different but related purpose.inplace_stop_token -
at_stop_requested
This pattern has existing precedent in the standard library for callback functions:-
atexit -
.at_quick_exit
-
Note that it is fairly trivial for users to create a thin wrapper which uses their desired naming:
template < typename T > class InplaceStoppableBase : std :: inplace_stoppable_base < InplaceStoppableBase < T >> { friend std :: inplace_stoppable_base < InplaceStoppableBase < T >> ; protected : using std :: inplace_stoppable_base < InplaceStoppableBase < T >>:: inplace_stoppable_base ; private : void on_stop_requested () noexcept { static_cast < T *> ( this ) -> OnStopRequested (); } };
2.2. Interaction with stop_callback_for_t
This proposal does not include any changes to
or the stop token concepts.
It is possible for users to handle arbitrary stop token types by specializing for types known to support CRTP and falling back to the callback type otherwise:
template < typename T , typename Token > class my_stoppable_base_for { class cb_t { my_stoppable_base_for & base ; public : explicit cb_t ( my_stoppable_base_for & base ) : base ( base ) {} void operator ()() { static_cast < T &> ( base ). on_stop_requested (); } }; std :: stop_callback_for_t < Token , cb_t > cb ; protected : explicit my_stoppable_base_for ( Token token ) : cb ( token ) {} }; template < typename T > class my_stoppable_base_for < T , std :: inplace_stop_token > : public std :: inplace_stoppable_base < T > { friend std :: inplace_stoppable_base < T > ; protected : using std :: inplace_stoppable_base :: inplace_stoppable_base ; };
3. Alternatives considered
3.1. inplace_stop_callback
optional parameter
Instead of introducing a new class template,
could be modified such that a reference to the
itself is passed in the invocation of its stop-callback member, if such an invocation is viable. In addition to no new standard library template, this avoids choosing a name to be imposed on the user operation state.
We prefer the proposed
over this approach mainly due to the increased complexity of user code in the common case:
class my_sender { template < OpState > struct cb_t { static void operator ()( std :: inplace_stop_callback < cb_t < OpState >>& cb ) { static_cast < OpState &> ( cb ). on_stop_requested (); } }; template < typename R > class op_state : std :: inplace_stop_callback < cb_t < op_state < R >>> { friend my_sender ; R receiver ; void on_stop_requested (); }; };
Note that both this modified
(under another user-chosen name) and
are implementable in terms of the other.
3.2. Superobject accessor function
A new standard library function template
which, given a reference to the stop-callback object, returns a reference to the enclosing
object:
class my_sender { template < OpState > struct cb_t { void operator ()() { using super_type = std :: inplace_stop_callback < cb_t < OpState >> ; static_cast < OpState &> ( std :: get_superobject < super_type &> ( * this )). on_stop_requested (); } }; template < typename R > class op_state : std :: inplace_stop_callback < cb_t < op_state >> { friend my_sender ; R receiver ; void on_stop_requested (); }; };
The other alternatives are implementable in terms of this option, while the reverse is not true. In that sense this the most general solution. However, apart from introducing this new function template and all the bikeshedding that comes with that, there is one major downside: In order to derive a reference to the
from a reference to its stop-callback member, one of three conditions must be fulfilled:
-
The stop-callback must be a base subobject of the
, which in turn requires the user callback type to be non-final.inplace_stop_callback -
The
must be a standard layout type, which in turn requires the user callback type itself to also be a standard layout type.inplace_stop_callback -
The standard library must use compiler magic not accessible to the user in the implementation of
.inplace_stop_callback
4. Implementation experience
This proposal has been implemented at github.com/vasama/stdexec.