30 KiB
[exec.snd.expos]
33 Execution control library [exec]
33.9 Senders [exec.snd]
33.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...).
The type FWD-ENV-T(Env) isdecltype(FWD-ENV(declval())).
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) 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 tosch.query(get_domain).
SCHED-ENV(sch) is an expression o2 whose type satisfies queryable such that o2.query(get_scheduler) is a prvalue with the same type and value as sch, and such that o2.query(get_domain) is expression-equivalent tosch.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) isTRY-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 expressionget_domain(get_completion_scheduler(get_env(sndr))).
Effects: If all of the typesCOMPL-DOMAIN(set_value_t),COMPL-DOMAIN(set_error_t), and
COMPL-DOMAIN(set_stopped_t) are ill-formed,completion-domain(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(sndr) is a default-constructed prvalue of that type.
Otherwise, completion-domain(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(std::forward(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:
get_domain(get_env(sndr))
completion-domain(sndr)
default_domain()
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, thenreturn Domain(); where Domain is the type of the following expression:[] {auto [_, sch, _] = sndr; return query-with-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:
get_domain(get_env(sndr))
completion-domain(sndr)
get_domain(env)
get_domain(get_scheduler(env))
default_domain()
template<callable Fun>requires is_nothrow_move_constructible_vstruct emplace-from { Fun fun; // exposition onlyusing type = call-result-t; constexpr operator type() && noexcept(nothrow-callable) {return std::move(fun)(); }constexpr type operator()() && noexcept(nothrow-callable) {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 onlyvoid operator()() noexcept { stop-src.request_stop(); }};
template<class T0, class T1, …, class Tn>struct product-type { // exposition only T0 t0; // exposition only T1 t1; // exposition only ⋮ Tn tn; // exposition onlytemplate<size_t I, class Self>constexpr decltype(auto) get(this Self&& self) noexcept; // exposition onlytemplate<class Self, class Fn>constexpr decltype(auto) apply(this Self&& self, Fn&& fn) // exposition onlynoexcept(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(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)(std::forward_like(ts)...);
Remarks: The expression in the noexcept clause is true if the return statement above is not potentially throwing; otherwise, false.
Let valid-specialization be the following concept:namespace std::execution {template<template<class...> class T, class... Args>concept valid-specialization = // exposition onlyrequires { typename T<Args...>; };}
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:
(sender && ...)
dependent_sender || sender_in,
where Sndr is basic-sender<Tag, Data,
Child...> as defined below.
Recommended practice: If evaluation of sender_in results in
an uncaught exception from
the evaluation of get_completion_signatures(),
the implementation should include information about that exception in
the resulting diagnostic.
Returns: A prvalue of type basic-sender<Tag, decay_t, decay_t...> that has been direct-list-initialized with the forwarded arguments, where basic-sender is the following exposition-only class template except as noted below.
Remarks: The default template argument for the Data template parameter denotes an unspecified empty trivially copyable class type that models semiregular.
namespace std::execution {templateconcept completion-tag = // exposition onlysame_as<Tag, set_value_t> || same_as<Tag, set_error_t> || same_as<Tag, set_stopped_t>; struct default-impls { // exposition onlystatic constexpr auto get-attrs = see below; // exposition onlystatic constexpr auto get-env = see below; // exposition onlystatic constexpr auto get-state = see below; // exposition onlystatic constexpr auto start = see below; // exposition onlystatic constexpr auto complete = see below; // exposition onlytemplate<class Sndr, class... Env>static consteval void check-types(); // exposition only}; templatestruct impls-for : default-impls {}; // exposition onlytemplate<class Sndr, class Rcvr> // exposition onlyusing state-type = decay_t<call-result-t<decltype(impls-for<tag_of_t>::get-state), Sndr, Rcvr&>>; template<class Index, class Sndr, class Rcvr> // exposition onlyusing env-type = call-result-t<decltype(impls-for<tag_of_t>::get-env), Index, state-type<Sndr, Rcvr>&, const Rcvr&>; templateusing data-type = decltype(declval().template get<1>()); // exposition onlytemplate<class Sndr, size_t I = 0>using child-type = decltype(declval().template get<I+2>()); // exposition onlytemplateusing indices-for = remove_reference_t::indices-for; // exposition onlytemplate<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>::get-state(std::forward(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 onlyusing receiver_concept = receiver_t; using tag-t = tag_of_t; // exposition onlyusing state-t = state-type<Sndr, Rcvr>; // exposition onlystatic constexpr const auto& complete = impls-for<tag-t>::complete; // exposition onlytemplate<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)...); }templaterequires 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(err)); }void set_stopped() && noexceptrequires 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::get-env(Index(), op->state, op->rcvr); }basic-state<Sndr, Rcvr>* op; // exposition only}; constexpr auto connect-all = see below; // exposition onlytemplate<class Sndr, class Rcvr>using connect-all-result = call-result-t< // exposition onlydecltype(connect-all), basic-state<Sndr, Rcvr>*, Sndr, indices-for>; 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 onlyusing operation_state_concept = operation_state_t; using tag-t = tag_of_t; // 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), std::move(rcvr)), inner-ops(connect-all(this, std::forward(sndr), indices-for())){}void start() & noexcept {auto& [...ops] = inner-ops; impls-for::start(this->state, this->rcvr, ops...); }}; template<class Tag, class Data, class... Child>struct basic-sender : product-type<Tag, Data, Child...> { // exposition onlyusing sender_concept = sender_t; using indices-for = index_sequence_for<Child...>; // exposition onlydecltype(auto) get_env() const noexcept {auto& [_, data, ...child] = *this; return impls-for::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), std::move(rcvr)}; }template<decays-to<basic-sender> Self, class... Env>static constexpr auto get_completion_signatures(); };}
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 isis_nothrow_move_constructible_v &¬hrow-callable<decltype(impls-for<tag_of_t>::get-state), Sndr, Rcvr&> &&(same_as<state-type<Sndr, Rcvr>, get-state-result> || is_nothrow_constructible_v<state-type<Sndr, Rcvr>, get-state-result>) where get-state-result iscall-result-t<decltype(impls-for<tag_of_t>::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), indices-for()))
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)), ...); elsereturn 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 allocator-aware-forward(std::forward_like(data), rcvr);}
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)...);}
template<class Sndr, class... Env> static consteval void default-impls::check-types();
Let Is be the pack of integral template arguments of the integer_sequence specialization denoted byindices-for.
Effects: Equivalent to:(get_completion_signatures<child-type<Sndr, Is>, FWD-ENV-T(Env)...>(), ...);
[Note 5:
For any types T and S, and pack E, let e be the expressionimpls-for::check-types<S, E...>().
Then exactly one of the following is true:
e is ill-formed, or
the evaluation of e exits with an exception, or
e is a core constant expression.
When e is a core constant expression, the pack S, E... uniquely determines a set of completion signatures.
â end note]
template<class Tag, class Data, class... Child> template<class Sndr, class... Env> constexpr auto basic-sender<Tag, Data, Child...>::get_completion_signatures();
Let Rcvr be the type of a receiver whose environment has type E, whereE is the first type in the list Env..., env<>.
Let CHECK-TYPES() be the expressionimpls-for::template check-types<
Sndr, E>(), and
let CS be a type determined as follows:
-
If CHECK-TYPES() is a core constant expression, let op be an lvalue subexpression whose type is connect_result_t<Sndr, Rcvr>. Then CS is the specialization of completion_signatures the set of whose template arguments correspond to the set of completion operations that are potentially evaluated ([basic.def.odr]) as a result of evaluating op.start().
-
Otherwise, CS is completion_signatures<>.
Constraints: CHECK-TYPES() is a well-formed expression.
Effects: Equivalent to:CHECK-TYPES();return CS();
template<class... Fns>struct overload-set : Fns... {using Fns::operator()...;};
struct not-a-sender {using sender_concept = sender_t; templatestatic consteval auto get_completion_signatures() -> completion_signatures<> {throw unspecified-exception(); }}; where unspecified-exception is a type derived from exception.
constexpr void decay-copyable-result-datums(auto cs) { cs.for-each([]<class Tag, class... Ts>(Tag(*)(Ts...)) {if constexpr (!(is_constructible_v<decay_t, Ts> &&...))throw unspecified-exception(); });} where unspecified-exception is a type derived from exception.
template<class T, class Context> decltype(auto) allocator-aware-forward(T&& obj, Context&& context); // exposition only
allocator-aware-forward is an exposition-only function template used to either create a new object of type remove_cvref_t from obj or forward obj depending on whether an allocator is available.
If the environment associated with context provides an allocator (i.e., the expression get_allocator(get_env(context)) is valid), let alloc be the result of this expression and let P be remove_cvref_t.
Returns:
If alloc is not defined, returns std::forward(obj),
otherwise if P is a specialization of product-type, returns an object of type P whose elements are initialized usingmake_obj_using_allocator<decltype(e)>(std::forward_like(e), alloc) where e is the corresponding element of obj,
otherwise, returns make_obj_using_allocator
(std::forward(obj), alloc).