33 Execution control library [exec]

33.9 Senders [exec.snd]

33.9.12 Sender adaptors [exec.adapt]

33.9.12.10 execution​::​split [exec.split]

split adapts an arbitrary sender into a sender that can be connected multiple times.
Let split-env be the type of an environment such that, given an instance env, the expression get_stop_token(env) is well-formed and has type inplace_stop_token.
The name split denotes a pipeable sender adaptor object.
For a subexpression sndr, let Sndr be decltype((sndr)).
If sender_in<Sndr, split-env> is false, split(sndr) is ill-formed.
Otherwise, the expression split(sndr) is expression-equivalent to: transform_sender(get-domain-early(sndr), make-sender(split, {}, sndr)) except that sndr is evaluated only once.
[Note 1: 
The default implementation of transform_sender will have the effect of connecting the sender to a receiver.
It will return a sender with a different tag type.
— end note]
Let local-state denote the following exposition-only class template:
namespace std::execution { struct local-state-base { // exposition only virtual ~local-state-base() = default; virtual void notify() noexcept = 0; // exposition only }; template<class Sndr, class Rcvr> struct local-state : local-state-base { // exposition only using on-stop-callback = // exposition only stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, on-stop-request>; local-state(Sndr&& sndr, Rcvr& rcvr) noexcept; ~local-state(); void notify() noexcept override; private: optional<on-stop-callback> on_stop; // exposition only shared-state<Sndr>* sh_state; // exposition only Rcvr* rcvr; // exposition only }; }
local-state(Sndr&& sndr, Rcvr& rcvr) noexcept;
Effects: Equivalent to: auto& [_, data, _] = sndr; this->sh_state = data.sh_state.get(); this->sh_state->inc-ref(); this->rcvr = addressof(rcvr);
~local-state();
Effects: Equivalent to: sh_state->dec-ref();
void notify() noexcept override;
Effects: Equivalent to: on_stop.reset(); visit( [this](const auto& tupl) noexcept -> void { apply( [this](auto tag, const auto&... args) noexcept -> void { tag(std::move(*rcvr), args...); }, tupl); }, sh_state->result);
Let split-receiver denote the following exposition-only class template: namespace std::execution { template<class Sndr> struct split-receiver { // exposition only using receiver_concept = receiver_t; template<class Tag, class... Args> void complete(Tag, Args&&... args) noexcept { // exposition only using tuple_t = decayed-tuple<Tag, Args...>; try { sh_state->result.template emplace<tuple_t>(Tag(), std::forward<Args>(args)...); } catch (...) { using tuple_t = tuple<set_error_t, exception_ptr>; sh_state->result.template emplace<tuple_t>(set_error, current_exception()); } sh_state->notify(); } template<class... Args> void set_value(Args&&... args) && noexcept { complete(execution::set_value, std::forward<Args>(args)...); } template<class Error> void set_error(Error&& err) && noexcept { complete(execution::set_error, std::forward<Error>(err)); } void set_stopped() && noexcept { complete(execution::set_stopped); } struct env { // exposition only shared-state<Sndr>* sh-state; // exposition only inplace_stop_token query(get_stop_token_t) const noexcept { return sh-state->stop_src.get_token(); } }; env get_env() const noexcept { return env{sh_state}; } shared-state<Sndr>* sh_state; // exposition only }; }
Let shared-state denote the following exposition-only class template: namespace std::execution { template<class Sndr> struct shared-state { using variant-type = see below; // exposition only using state-list-type = see below; // exposition only explicit shared-state(Sndr&& sndr); void start-op() noexcept; // exposition only void notify() noexcept; // exposition only void inc-ref() noexcept; // exposition only void dec-ref() noexcept; // exposition only inplace_stop_source stop_src{}; // exposition only variant-type result{}; // exposition only state-list-type waiting_states; // exposition only atomic<bool> completed{false}; // exposition only atomic<size_t> ref_count{1}; // exposition only connect_result_t<Sndr, split-receiver<Sndr>> op_state; // exposition only }; }
Let Sigs be a pack of the arguments to the completion_signatures specialization named by completion_signatures_of_t<Sndr>.
For type Tag and pack Args, let as-tuple be an alias template such that as-tuple<Tag(Args...)> denotes the type decayed-tuple<Tag, Args...>.
Then variant-type denotes the type variant<tuple<set_stopped_t>, tuple<set_error_t, exception_ptr>, as-tuple<Sigs>...> but with duplicate types removed.
Let state-list-type be a type that stores a list of pointers to local-state-base objects and that permits atomic insertion.
explicit shared-state(Sndr&& sndr);
Effects: Initializes op_state with the result of connect(std​::​forward<Sndr>(sndr), split-receiver{this}).
Postconditions: waiting_states is empty, and completed is false.
void start-op() noexcept;
Effects: Evaluates inc-ref().
If stop_src.stop_requested() is true, evaluates notify(); otherwise, evaluates start(op_state).
void notify() noexcept;
Effects: Atomically does the following:
  • Sets completed to true, and
  • Exchanges waiting_states with an empty list, storing the old value in a local prior_states.
Then, for each pointer p in prior_states, evaluates p->notify().
Finally, evaluates dec-ref().
void inc-ref() noexcept;
Effects: Increments ref_count.
void dec-ref() noexcept;
Effects: Decrements ref_count.
If the new value of ref_count is 0, calls delete this.
Synchronization: If an evaluation of dec-ref() does not decrement the ref_count to 0 then synchronizes with the evaluation of dec-ref() that decrements ref_count to 0.
Let split-impl-tag be an empty exposition-only class type.
Given an expression sndr, the expression split.transform_sender(sndr) is equivalent to: auto&& [tag, _, child] = sndr; auto* sh_state = new shared-state{std::forward_like<decltype((sndr))>(child)}; return make-sender(split-impl-tag(), shared-wrapper{sh_state, tag}); where shared-wrapper is an exposition-only class that manages the reference count of the shared-state object pointed to by sh_state.
shared-wrapper models copyable with move operations nulling out the moved-from object, copy operations incrementing the reference count by calling sh_state->inc-ref(), and assignment operations performing a copy-and-swap operation.
The destructor has no effect if sh_state is null; otherwise, it decrements the reference count by evaluating sh_state->dec-ref().
The exposition-only class template impls-for ([exec.snd.general]) is specialized for split-impl-tag as follows: namespace std::execution { template<> struct impls-for<split-impl-tag> : default-impls { static constexpr auto get-state = see below; static constexpr auto start = see below; }; }
The member impls-for<split-impl-tag>​::​get-state is initialized with a callable object equivalent to the following lambda expression: []<class Sndr>(Sndr&& sndr, auto& rcvr) noexcept { return local-state{std::forward<Sndr>(sndr), rcvr}; }
The member impls-for<split-impl-tag>​::​start is initialized with a callable object that has a function call operator equivalent to the following: template<class Sndr, class Rcvr> void operator()(local-state<Sndr, Rcvr>& state, Rcvr& rcvr) const noexcept;
Effects: If state.sh_state->completed is true, evaluates state.notify() and returns.
Otherwise, does the following in order:
  • Evaluates state.on_stop.emplace( get_stop_token(get_env(rcvr)), on-stop-request{state.sh_state->stop_src});
  • Then atomically does the following:
    • Reads the value c of state.sh_state->completed, and
    • Inserts addressof(state) into state.sh_state->waiting_states if c is false.
  • If c is true, calls state.notify() and returns.
  • Otherwise, if addressof(state) is the first item added to state.sh_state->waiting_states, evaluates state.sh_state->start-op().