11 KiB
[exec.let]
33 Execution control library [exec]
33.9 Senders [exec.snd]
33.9.12 Sender adaptors [exec.adapt]
33.9.12.10 execution::let_value, execution::let_error, execution::let_stopped [exec.let]
let_value, let_error, and let_stopped transform a sender's value, error, and stopped completions, respectively, into a new child asynchronous operation by passing the sender's result datums to a user-specified callable, which returns a new sender that is connected and started.
For let_value, let_error, and let_stopped, let set-cpo beset_value, set_error, and set_stopped, respectively.
Let the expression let-cpo be one oflet_value, let_error, or let_stopped.
For a subexpression sndr, let let-env(sndr) be expression-equivalent to the first well-formed expression below:
SCHED-ENV(get_completion_scheduler<decayed-typeof<set-cpo>>(get_env(sndr)))
MAKE-ENV(get_domain, get_domain(get_env(sndr)))
(void(sndr), env<>{})
The names let_value, let_error, and let_stopped denote pipeable sender adaptor objects.
For subexpressions sndr and f, let F be the decayed type of f.
If decltype((sndr)) does not satisfy sender or if decltype((f)) does not satisfy movable-value, the expression let-cpo(sndr, f) is ill-formed.
If F does not satisfy invocable, the expression let_stopped(sndr, f) is ill-formed.
Otherwise, the expression let-cpo(sndr, f) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(let-cpo, f, sndr)) except that sndr is evaluated only once.
The exposition-only class template impls-for ([exec.snd.expos]) is specialized for let-cpo as follows:
namespace std::execution {template<class State, class Rcvr, class... Args>void let-bind(State& state, Rcvr& rcvr, Args&&... args); // exposition onlytemplate<>struct impls-for<decayed-typeof<let-cpo>> : default-impls {static constexpr auto get-state = see below; static constexpr auto complete = see below; template<class Sndr, class... Env>static consteval void check-types(); };}
Let receiver2 denote the following exposition-only class template:namespace std::execution {template<class Rcvr, class Env>struct receiver2 {using receiver_concept = receiver_t; template<class... Args>void set_value(Args&&... args) && noexcept { execution::set_value(std::move(rcvr), std::forward(args)...); }templatevoid set_error(Error&& err) && noexcept { execution::set_error(std::move(rcvr), std::forward(err)); }void set_stopped() && noexcept { execution::set_stopped(std::move(rcvr)); }decltype(auto) get_env() const noexcept {return see below; } Rcvr& rcvr; // exposition only Env env; // exposition only};}
Invocation of the function receiver2::get_env returns an object e such that
decltype(e) models queryable and
given a query object q, the expression e.query(q) is expression-equivalent to env.query(q) if that expression is valid; otherwise, if the type of q satisfies forwarding-query,e.query(q) is expression-equivalent to get_env(rcvr).query(q); otherwise,e.query(q) is ill-formed.
template<class Sndr, class... Env> static consteval void check-types();
Effects: Equivalent to:using LetFn = remove_cvref_t<data-type>;auto cs = get_completion_signatures<child-type, FWD-ENV-T(Env)...>();auto fn = []<class... Ts>(decayed-typeof<set-cpo>(*)(Ts...)) {if constexpr (!is-valid-let-sender) // see belowthrow unspecified-exception();}; cs.for-each(overload-set(fn, {})); where unspecified-exception is a type derived from exception, and where is-valid-let-sender is true if and only if all of the following are true:
(constructible_from<decay_t, Ts> &&...)
invocable<LetFn, decay_t&...>
sender<invoke_result_t<LetFn, decay_t&...>>
sizeof...(Env) == 0 || sender_in<invoke_result_t<LetFn, decay_t&...>, env-t
...>
where env-t is the packdecltype(let-cpo.transform_env(declval(), declval())).
impls-for<decayed-typeof<let-cpo>>::get-state is initialized with a callable object equivalent to the following:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) requires see below {auto& [_, fn, child] = sndr; using fn_t = decay_t<decltype(fn)>; using env_t = decltype(let-env(child)); using args_variant_t = see below; using ops2_variant_t = see below; struct state-type { fn_t fn; // exposition only env_t env; // exposition only args_variant_t args; // exposition only ops2_variant_t ops2; // exposition only}; return state-type{allocator-aware-forward(std::forward_like(fn), rcvr), let-env(child), {}, {}};}
Let Sigs be a pack of the arguments to the completion_signatures specialization named bycompletion_signatures_of_t<child-type, FWD-ENV-T(env_of_t)>.
Let LetSigs be a pack of those types in Sigs with a return type of decayed-typeof<set-cpo>.
Let as-tuple be an alias template such that as-tuple<Tag(Args...)> denotes the type decayed-tuple<Args...>.
Then args_variant_t denotes the type variant<monostate, as-tuple...> except with duplicate types removed.
Given a type Tag and a pack Args, let as-sndr2 be an alias template such that as-sndr2<Tag(Args...)> denotes the type call-result-t<F, decay_t&...>.
Then ops2_variant_t denotes the typevariant<monostate, connect_result_t<as-sndr2, receiver2<Rcvr, env_t>>...> except with duplicate types removed.
The requires-clause constraining the above lambda is satisfied if and only if the types args_variant_t and ops2_variant_t are well-formed.
The exposition-only function template let-bind has effects equivalent to:using args_t = decayed-tuple<Args...>;auto mkop2 = [&] {return connect( apply(std::move(state.fn), state.args.template emplace<args_t>(std::forward(args)...)), receiver2{rcvr, std::move(state.env)});}; start(state.ops2.template emplace<decltype(mkop2())>(emplace-from{mkop2}));
impls-for<decayed-typeof>::complete is initialized with a callable object equivalent to the following:[]<class Tag, class... Args>(auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept -> void {if constexpr (same_as<Tag, decayed-typeof<set-cpo>>) {TRY-EVAL(rcvr, let-bind(state, rcvr, std::forward(args)...)); } else { Tag()(std::move(rcvr), std::forward(args)...); }}
Let sndr and env be subexpressions, and let Sndr be decltype((sndr)).
Ifsender-for<Sndr, decayed-typeof<let-cpo>> is false, then the expression let-cpo.transform_env(sndr, env) is ill-formed.
Otherwise, it is equal to:auto& [_, _, child] = sndr;return JOIN-ENV(let-env(child), FWD-ENV(env));
Let the subexpression out_sndr denote the result of the invocation let-cpo(sndr, f) or an object equal to such, and let the subexpression rcvr denote a receiver such that the expression connect(out_sndr, rcvr) is well-formed.
The expression connect(out_sndr, rcvr) has undefined behavior unless it creates an asynchronous operation ([exec.async.ops]) that, when started:
invokes f when set-cpo is called with sndr's result datums,
makes its completion dependent on the completion of a sender returned by f, and
propagates the other completion operations sent by sndr.