34 Execution control library [exec]

34.9 Senders [exec.snd]

34.9.2 Exposition-only entities [exec.snd.expos]

Subclause [exec.snd] makes use of the following exposition-only entities.
For a queryable object env, FWD-ENV(env) is an expression whose type satisfies queryable such that for a query object q and a pack of subexpressions as, the expression FWD-ENV(env).query(q, as...) is ill-formed if forwarding_query(q) is false; otherwise, it is expression-equivalent to env.query(q, as...).
For a query object q and a subexpression v, MAKE-ENV(q, v) is an expression env whose type satisfies queryable such that the result of env.query(q) has a value equal to v ([concepts.equality]).
Unless otherwise stated, the object to which env.query(q) refers remains valid while env remains valid.
For two queryable objects env1 and env2, a query object q, and a pack of subexpressions as, JOIN-ENV(env1, env2) is an expression env3 whose type satisfies queryable such that env3.query(q, as...) is expression-equivalent to:
  • env1.query(q, as...) if that expression is well-formed,
  • otherwise, env2.query(q, as...) if that expression is well-formed,
  • otherwise, env3.query(q, as...) is ill-formed.
The results of FWD-ENV, MAKE-ENV, and JOIN-ENV can be context-dependent; i.e., they can evaluate to expressions with different types and value categories in different contexts for the same arguments.
For a scheduler sch, SCHED-ATTRS(sch) is an expression o1 whose type satisfies queryable such that o1.query(get_completion_scheduler<Tag>) is an expression with the same type and value as sch where Tag is one of set_value_t or set_stopped_t, and such that o1.query(get_domain) is expression-equivalent to sch.query(get_domain).
SCHED-ENV(sch) is an expression o2 whose type satisfies queryable such that o1.query(get_scheduler) is a prvalue with the same type and value as sch, and such that o2.query(get_domain) is expression-equivalent to sch.query(get_domain).
For two subexpressions rcvr and expr, SET-VALUE(rcvr, expr) is expression-equivalent to (expr, set_value(std​::​move(rcvr))) if the type of expr is void; otherwise, set_value(std​::​move(rcvr), expr).
TRY-EVAL(rcvr, expr) is equivalent to: try { expr; } catch(...) { set_error(std::move(rcvr), current_exception()); } if expr is potentially-throwing; otherwise, expr.
TRY-SET-VALUE(rcvr, expr) is TRY-EVAL(rcvr, SET-VALUE(rcvr, expr)) except that rcvr is evaluated only once.
template<class Default = default_domain, class Sndr> constexpr auto completion-domain(const Sndr& sndr) noexcept;
COMPL-DOMAIN(T) is the type of the expression get_domain(get_completion_scheduler<T>(get_env(sndr))).
Effects: If all of the types COMPL-DOMAIN(set_value_t), COMPL-DOMAIN(set_error_t), and COMPL-DOMAIN(set_stopped_t) are ill-formed, completion-domain<Default>(sndr) is a default-constructed prvalue of type Default.
Otherwise, if they all share a common type ([meta.trans.other]) (ignoring those types that are ill-formed), then completion-domain<Default>(sndr) is a default-constructed prvalue of that type.
Otherwise, completion-domain<Default>(sndr) is ill-formed.
template<class Tag, class Env, class Default> constexpr decltype(auto) query-with-default( Tag, const Env& env, Default&& value) noexcept(see below);
Let e be the expression Tag()(env) if that expression is well-formed; otherwise, it is static_cast<Default>(std​::​forward<Default>(value)).
Returns: e.
Remarks: The expression in the noexcept clause is noexcept(e).
template<class Sndr> constexpr auto get-domain-early(const Sndr& sndr) noexcept;
Effects: Equivalent to: return Domain(); where Domain is the decayed type of the first of the following expressions that is well-formed:
template<class Sndr, class Env> constexpr auto get-domain-late(const Sndr& sndr, const Env& env) noexcept;
Effects: Equivalent to:
  • If sender-for<Sndr, continues_on_t> is true, then return Domain(); where Domain is the type of the following expression: [] { auto [_, sch, _] = sndr; return query-or-default(get_domain, sch, default_domain()); }();
    [Note 1: 
    The continues_on algorithm works in tandem with schedule_from ([exec.schedule.from]) to give scheduler authors a way to customize both how to transition onto (continues_on) and off of (schedule_from) a given execution context.
    Thus, continues_on ignores the domain of the predecessor and uses the domain of the destination scheduler to select a customization, a property that is unique to continues_on.
    That is why it is given special treatment here.
    — end note]
  • Otherwise, return Domain(); where Domain is the first of the following expressions that is well-formed and whose type is not void:
template<callable Fun> requires is_nothrow_move_constructible_v<Fun> struct emplace-from { Fun fun; // exposition only using type = call-result-t<Fun>; constexpr operator type() && noexcept(nothrow-callable<Fun>) { return std::move(fun)(); } constexpr type operator()() && noexcept(nothrow-callable<Fun>) { return std::move(fun)(); } };
[Note 2: 
emplace-from is used to emplace non-movable types into tuple, optional, variant, and similar types.
— end note]
struct on-stop-request { inplace_stop_source& stop-src; // exposition only void operator()() noexcept { stop-src.request_stop(); } };
template<class T, class T, ..., class T> struct product-type { // exposition only T t; // exposition only T t; // exposition only ... T t; // exposition only template<size_t I, class Self> constexpr decltype(auto) get(this Self&& self) noexcept; // exposition only template<class Self, class Fn> constexpr decltype(auto) apply(this Self&& self, Fn&& fn) // exposition only noexcept(see below); };
[Note 3: 
product-type is presented here in pseudo-code form for the sake of exposition.
It can be approximated in standard C++ with a tuple-like implementation that takes care to keep the type an aggregate that can be used as the initializer of a structured binding declaration.
— end note]
[Note 4: 
An expression of type product-type is usable as the initializer of a structured binding declaration ([dcl.struct.bind]).
— end note]
template<size_t I, class Self> constexpr decltype(auto) get(this Self&& self) noexcept;
Effects: Equivalent to: auto& [...ts] = self; return std::forward_like<Self>(ts...[I]);
template<class Self, class Fn> constexpr decltype(auto) apply(this Self&& self, Fn&& fn) noexcept(see below);
Constraints: The expression in the return statement below is well-formed.
Effects: Equivalent to: auto& [...ts] = self; return std::forward<Fn>(fn)(std::forward_like<Self>(ts)...);
Remarks: The expression in the noexcept clause is true if the return statement above is not potentially throwing; otherwise, false.
template<class Tag, class Data = see below, class... Child> constexpr auto make-sender(Tag tag, Data&& data, Child&&... child);
Mandates: The following expressions are true:
Returns: A prvalue of type basic-sender<Tag, decay_t<Data>, decay_t<Child>...> that has been direct-list-initialized with the forwarded arguments, where basic-sender is the following exposition-only class template except as noted below.
namespace std::execution { template<class Tag> concept completion-tag = // exposition only same_as<Tag, set_value_t> || same_as<Tag, set_error_t> || same_as<Tag, set_stopped_t>; template<template<class...> class T, class... Args> concept valid-specialization = // exposition only requires { typename T<Args...>; }; struct default-impls { // exposition only static constexpr auto get-attrs = see below; // exposition only static constexpr auto get-env = see below; // exposition only static constexpr auto get-state = see below; // exposition only static constexpr auto start = see below; // exposition only static constexpr auto complete = see below; // exposition only }; template<class Tag> struct impls-for : default-impls {}; // exposition only template<class Sndr, class Rcvr> // exposition only using state-type = decay_t<call-result-t< decltype(impls-for<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&>>; template<class Index, class Sndr, class Rcvr> // exposition only using env-type = call-result-t< decltype(impls-for<tag_of_t<Sndr>>::get-env), Index, state-type<Sndr, Rcvr>&, const Rcvr&>; template<class Sndr, size_t I = 0> using child-type = decltype(declval<Sndr>().template get<I+2>()); // exposition only template<class Sndr> using indices-for = remove_reference_t<Sndr>::indices-for; // exposition only template<class Sndr, class Rcvr> struct basic-state { // exposition only basic-state(Sndr&& sndr, Rcvr&& rcvr) noexcept(see below) : rcvr(std::move(rcvr)) , state(impls-for<tag_of_t<Sndr>>::get-state(std::forward<Sndr>(sndr), rcvr)) { } Rcvr rcvr; // exposition only state-type<Sndr, Rcvr> state; // exposition only }; template<class Sndr, class Rcvr, class Index> requires valid-specialization<env-type, Index, Sndr, Rcvr> struct basic-receiver { // exposition only using receiver_concept = receiver_t; using tag-t = tag_of_t<Sndr>; // exposition only using state-t = state-type<Sndr, Rcvr>; // exposition only static constexpr const auto& complete = impls-for<tag-t>::complete; // exposition only template<class... Args> requires callable<decltype(complete), Index, state-t&, Rcvr&, set_value_t, Args...> void set_value(Args&&... args) && noexcept { complete(Index(), op->state, op->rcvr, set_value_t(), std::forward<Args>(args)...); } template<class Error> requires callable<decltype(complete), Index, state-t&, Rcvr&, set_error_t, Error> void set_error(Error&& err) && noexcept { complete(Index(), op->state, op->rcvr, set_error_t(), std::forward<Error>(err)); } void set_stopped() && noexcept requires callable<decltype(complete), Index, state-t&, Rcvr&, set_stopped_t> { complete(Index(), op->state, op->rcvr, set_stopped_t()); } auto get_env() const noexcept -> env-type<Index, Sndr, Rcvr> { return impls-for<tag-t>::get-env(Index(), op->state, op->rcvr); } basic-state<Sndr, Rcvr>* op; // exposition only }; constexpr auto connect-all = see below; // exposition only template<class Sndr, class Rcvr> using connect-all-result = call-result-t< // exposition only decltype(connect-all), basic-state<Sndr, Rcvr>*, Sndr, indices-for<Sndr>>; template<class Sndr, class Rcvr> requires valid-specialization<state-type, Sndr, Rcvr> && valid-specialization<connect-all-result, Sndr, Rcvr> struct basic-operation : basic-state<Sndr, Rcvr> { // exposition only using operation_state_concept = operation_state_t; using tag-t = tag_of_t<Sndr>; // exposition only connect-all-result<Sndr, Rcvr> inner-ops; // exposition only basic-operation(Sndr&& sndr, Rcvr&& rcvr) noexcept(see below) // exposition only : basic-state<Sndr, Rcvr>(std::forward<Sndr>(sndr), std::move(rcvr)), inner-ops(connect-all(this, std::forward<Sndr>(sndr), indices-for<Sndr>())) {} void start() & noexcept { auto& [...ops] = inner-ops; impls-for<tag-t>::start(this->state, this->rcvr, ops...); } }; template<class Sndr, class Env> using completion-signatures-for = see below; // exposition only template<class Tag, class Data, class... Child> struct basic-sender : product-type<Tag, Data, Child...> { // exposition only using sender_concept = sender_t; using indices-for = index_sequence_for<Child...>; // exposition only decltype(auto) get_env() const noexcept { auto& [_, data, ...child] = *this; return impls-for<Tag>::get-attrs(data, child...); } template<decays-to<basic-sender> Self, receiver Rcvr> auto connect(this Self&& self, Rcvr rcvr) noexcept(see below) -> basic-operation<Self, Rcvr> { return {std::forward<Self>(self), std::move(rcvr)}; } template<decays-to<basic-sender> Self, class Env> auto get_completion_signatures(this Self&& self, Env&& env) noexcept -> completion-signatures-for<Self, Env> { return {}; } }; }
The default template argument for the Data template parameter denotes an unspecified empty trivially copyable class type that models semiregular.
It is unspecified whether a specialization of basic-sender is an aggregate.
An expression of type basic-sender is usable as the initializer of a structured binding declaration ([dcl.struct.bind]).
The expression in the noexcept clause of the constructor of basic-state is: is_nothrow_move_constructible_v<Rcvr> && nothrow-callable<decltype(impls-for<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&>
The object connect-all is initialized with a callable object equivalent to the following lambda:
[]<class Sndr, class Rcvr, size_t... Is>( basic-state<Sndr, Rcvr>* op, Sndr&& sndr, index_sequence<Is...>) noexcept(see below) -> decltype(auto) { auto& [_, data, ...child] = sndr; return product-type{connect( std::forward_like<Sndr>(child), basic-receiver<Sndr, Rcvr, integral_constant<size_t, Is>>{op})...}; }
Constraints: The expression in the return statement is well-formed.
Remarks: The expression in the noexcept clause is true if the return statement is not potentially throwing; otherwise, false.
The expression in the noexcept clause of the constructor of basic-operation is: is_nothrow_constructible_v<basic-state<Self, Rcvr>, Self, Rcvr> && noexcept(connect-all(this, std::forward<Sndr>(sndr), indices-for<Sndr>()))
The expression in the noexcept clause of the connect member function of basic-sender is: is_nothrow_constructible_v<basic-operation<Self, Rcvr>, Self, Rcvr>
The member default-impls​::​get-attrs is initialized with a callable object equivalent to the following lambda: [](const auto&, const auto&... child) noexcept -> decltype(auto) { if constexpr (sizeof...(child) == 1) return (FWD-ENV(get_env(child)), ...); else return empty_env(); }
The member default-impls​::​get-env is initialized with a callable object equivalent to the following lambda: [](auto, auto&, const auto& rcvr) noexcept -> decltype(auto) { return FWD-ENV(get_env(rcvr)); }
The member default-impls​::​get-state is initialized with a callable object equivalent to the following lambda: []<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept -> decltype(auto) { auto& [_, data, ...child] = sndr; return std::forward_like<Sndr>(data); }
The member default-impls​::​start is initialized with a callable object equivalent to the following lambda: [](auto&, auto&, auto&... ops) noexcept -> void { (execution::start(ops), ...); }
The member default-impls​::​complete is initialized with a callable object equivalent to the following lambda: []<class Index, class Rcvr, class Tag, class... Args>( Index, auto& state, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void requires callable<Tag, Rcvr, Args...> { static_assert(Index::value == 0); Tag()(std::move(rcvr), std::forward<Args>(args)...); }
For a subexpression sndr let Sndr be decltype((sndr)).
Let rcvr be a receiver with an associated environment of type Env such that sender_in<Sndr, Env> is true.
completion-signatures-for<Sndr, Env> denotes a specialization of completion_signatures, the set of whose template arguments correspond to the set of completion operations that are potentially evaluated as a result of starting ([exec.async.ops]) the operation state that results from connecting sndr and rcvr.
When sender_in<Sndr, Env> is false, the type denoted by completion-signatures-for<Sndr, Env>, if any, is not a specialization of completion_signatures.
Recommended practice: When sender_in<Sndr, Env> is false, implementations are encouraged to use the type denoted by completion-signatures-for<Sndr, Env> to communicate to users why.
template<sender Sndr, queryable Env> constexpr auto write-env(Sndr&& sndr, Env&& env); // exposition only
write-env is an exposition-only sender adaptor that, when connected with a receiver rcvr, connects the adapted sender with a receiver whose execution environment is the result of joining the queryable argument env to the result of get_env(rcvr).
Let write-env-t be an exposition-only empty class type.
Returns: make-sender(write-env-t(), std::forward<Env>(env), std::forward<Sndr>(sndr))
Remarks: The exposition-only class template impls-for ([exec.snd.general]) is specialized for write-env-t as follows: template<> struct impls-for<write-env-t> : default-impls { static constexpr auto get-env = [](auto, const auto& state, const auto& rcvr) noexcept { return JOIN-ENV(state, get_env(rcvr)); }; };