33 Execution control library [exec]

33.14 Execution scope utilities [exec.scope]

33.14.2 Counting Scopes [exec.counting.scopes]

33.14.2.1 General [exec.counting.scopes.general]

Scopes of type simple_counting_scope and counting_scope maintain counts of associations.
Let:
  • Scope be either simple_counting_scope or counting_scope,
  • scope be an object of type Scope,
  • tkn be an object of type Scope​::​token obtained from scope.get_token(),
  • jsndr be a sender obtained from scope.join(), and
  • op be an operation state obtained from connecting jsndr to a receiver.
During its lifetime scope goes through different states which govern what operations are allowed and the result of these operations:
  • unused: a newly constructed object starts in the unused state.
  • open: when tkn.try_associate() is called while scope is in the unused state, scope moves to the open state.
  • open-and-joining: when the operation state op is started while scope is in the unused or open state, scope moves to the open-and-joining state.
  • closed: when scope.close() is called while scope is in the open state, scope moves to the closed state.
  • unused-and-closed: when scope.close() is called while scope is in the unused state, scope moves to the unused-and-closed state.
  • closed-and-joining: when scope.close() is called while scope is in the open-and-joining state or the operation state op is started while scope is in the closed or unused-and-closed state, scope moves to the closed-and-joining state.
  • joined: when the count of assocations drops to zero while scope is in the open-and-joining or closed-and-joining state, scope moves to the joined state.
Recommended practice: For simple_counting_scope and counting_scope, implementations should store the state and the count of associations in a single member of type size_t.
Subclause [exec.counting.scopes] makes use of the following exposition-only entities:
struct scope-join-t {}; // exposition only enum scope-state-type { // exposition only unused, // exposition only open, // exposition only closed, // exposition only open-and-joining, // exposition only closed-and-joining, // exposition only unused-and-closed, // exposition only joined, // exposition only };
The exposition-only class template impls-for ([exec.snd.general]) is specialized for scope-join-t as follows:
namespace std::execution { template<> struct impls-for<scope-join-t> : default-impls { template<class Scope, class Rcvr> struct state { // exposition only struct rcvr-t { // exposition only using receiver_concept = receiver_t; Rcvr& rcvr; // exposition only void set_value() && noexcept { execution::set_value(std::move(rcvr)); } template<class E> void set_error(E&& e) && noexcept { execution::set_error(std::move(rcvr), std::forward<E>(e)); } void set_stopped() && noexcept { execution::set_stopped(std::move(rcvr)); } decltype(auto) get_env() const noexcept { return execution::get_env(rcvr); } }; using sched-sender = // exposition only decltype(schedule(get_scheduler(get_env(declval<Rcvr&>())))); using op-t = // exposition only connect_result_t<sched-sender, rcvr-t>; Scope* scope; // exposition only Rcvr& receiver; // exposition only op-t op; // exposition only state(Scope* scope, Rcvr& rcvr) // exposition only noexcept(nothrow-callable<connect_t, sched-sender, rcvr-t>) : scope(scope), receiver(rcvr), op(connect(schedule(get_scheduler(get_env(rcvr))), rcvr-t(rcvr))) {} void complete() noexcept { // exposition only start(op); } void complete-inline() noexcept { // exposition only set_value(std::move(receiver)); } }; static constexpr auto get-state = // exposition only []<class Rcvr>(auto&& sender, Rcvr& receiver) noexcept(is_nothrow_constructible_v<state<Rcvr>, data-type<decltype(sender)>, Rcvr&>) { auto[_, self] = sender; return state(self, receiver); }; static constexpr auto start = // exposition only [](auto& s, auto&) noexcept { if (s.scope->start-join-sender(s)) s.complete-inline(); }; }; }