[exec.let] # 33 Execution control library [[exec]](./#exec) ## 33.9 Senders [[exec.snd]](exec.snd#exec.let) ### 33.9.12 Sender adaptors [[exec.adapt]](exec.adapt#exec.let) #### 33.9.12.10 execution​::​let_value, execution​::​let_error, execution​::​let_stopped [exec.let] [1](#1) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L3847) 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[.](#1.sentence-1) [2](#2) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L3854) For let_value, let_error, and let_stopped, let *set-cpo* beset_value, set_error, and set_stopped, respectively[.](#2.sentence-1) Let the expression *let-cpo* be one oflet_value, let_error, or let_stopped[.](#2.sentence-2) For a subexpression sndr, let *let-env*(sndr) be expression-equivalent to the first well-formed expression below: - [(2.1)](#2.1) *SCHED-ENV*(get_completion_scheduler<*decayed-typeof*<*set-cpo*>>(get_env(sndr))) - [(2.2)](#2.2) *MAKE-ENV*(get_domain, get_domain(get_env(sndr))) - [(2.3)](#2.3) (void(sndr), env<>{}) [3](#3) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L3872) The names let_value, let_error, and let_stopped denote pipeable sender adaptor objects[.](#3.sentence-1) For subexpressions sndr and f, let F be the decayed type of f[.](#3.sentence-2) If decltype((sndr)) does not satisfy [sender](exec.snd.concepts#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") or if decltype((f)) does not satisfy [*movable-value*](exec.general#concept:movable-value "33.1 General [exec.general]"), the expression *let-cpo*(sndr, f) is ill-formed[.](#3.sentence-3) If F does not satisfy [invocable](concept.invocable#concept:invocable "18.7.2 Concept invocable [concept.invocable]"), the expression let_stopped(sndr, f) is ill-formed[.](#3.sentence-4) [4](#4) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L3883) 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[.](#4.sentence-1) [5](#5) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L3891) The exposition-only class template *impls-for* ([[exec.snd.expos]](exec.snd.expos "33.9.2 Exposition-only entities")) is specialized for *let-cpo* as follows: [🔗](#lib:impls-for%3cdecayed-typeof%3clet-cpo%3e%3e) namespace std::execution {templatevoid *let-bind*(State& state, Rcvr& rcvr, Args&&... args); // *exposition only*template<>struct *impls-for*<*decayed-typeof*<*let-cpo*>> : *default-impls* {static constexpr auto *get-state* = *see below*; static constexpr auto *complete* = *see below*; templatestatic consteval void *check-types*(); };} [6](#6) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L3911) Let *receiver2* denote the following exposition-only class template:namespace std::execution {templatestruct *receiver2* {using receiver_concept = receiver_t; templatevoid 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 - [(6.1)](#6.1) decltype(e) models [*queryable*](exec.queryable.concept#concept:queryable "33.2.2 queryable concept [exec.queryable.concept]") and - [(6.2)](#6.2) 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*](execution.syn#concept:forwarding-query "33.4 Header synopsis [execution.syn]"),e.query(q) is expression-equivalent to get_env(*rcvr*).query(q); otherwise,e.query(q) is ill-formed[.](#6.sentence-2) [🔗](#lib:check-types,impls-for%3cdecayed-typeof%3clet-cpo%3e%3e) `template static consteval void check-types(); ` [7](#7) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L3966) *Effects*: Equivalent to:using LetFn = remove_cvref_t<*data-type*>;auto cs = get_completion_signatures<*child-type*, *FWD-ENV-T*(Env)...>();auto fn = [](*decayed-typeof*<*set-cpo*>(*)(Ts...)) {if constexpr (!*is-valid-let-sender*) // *see below*throw *unspecified-exception*();}; cs.*for-each*(*overload-set*(fn, [](auto){})); 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: - [(7.1)](#7.1) ([constructible_from](concept.constructible#concept:constructible_from "18.4.11 Concept constructible_­from [concept.constructible]"), Ts> &&...) - [(7.2)](#7.2) [invocable](concept.invocable#concept:invocable "18.7.2 Concept invocable [concept.invocable]")&...> - [(7.3)](#7.3) [sender](exec.snd.concepts#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]")&...>> - [(7.4)](#7.4) sizeof...(Env) == 0 || [sender_in](exec.snd.concepts#concept:sender_in "33.9.3 Sender concepts [exec.snd.concepts]")&...>, *env-t* ...> where *env-t* is the packdecltype(*let-cpo*.transform_env(declval(), declval()))[.](#7.sentence-1) [8](#8) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L3993) *impls-for*<*decayed-typeof*<*let-cpo*>>​::​*get-state* is initialized with a callable object equivalent to the following:[](Sndr&& sndr, Rcvr& rcvr) requires *see below* {auto& [_, fn, child] = sndr; using fn_t = decay_t; 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), {}, {}};} [9](#9) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4015) 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)>[.](#9.sentence-1) Let LetSigs be a pack of those types in Sigs with a return type of *decayed-typeof*<*set-cpo*>[.](#9.sentence-2) Let *as-tuple* be an alias template such that *as-tuple* denotes the type *decayed-tuple*[.](#9.sentence-3) Then args_variant_t denotes the type variant...> except with duplicate types removed[.](#9.sentence-4) [10](#10) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4028) Given a type Tag and a pack Args, let *as-sndr2* be an alias template such that *as-sndr2* denotes the type *call-result-t*&...>[.](#10.sentence-1) Then ops2_variant_t denotes the typevariant, *receiver2*>...> except with duplicate types removed[.](#10.sentence-2) [11](#11) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4040) The [*requires-clause*](temp.pre#nt:requires-clause "13.1 Preamble [temp.pre]") constraining the above lambda is satisfied if and only if the types args_variant_t and ops2_variant_t are well-formed[.](#11.sentence-1) [12](#12) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4045) The exposition-only function template *let-bind* has effects equivalent to:using args_t = *decayed-tuple*;auto mkop2 = [&] {return connect( apply(std::move(state.fn), state.args.template emplace(std::forward(args)...)), *receiver2*{rcvr, std::move(state.env)});}; start(state.ops2.template emplace(*emplace-from*{mkop2})); [13](#13) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4059) *impls-for*<*decayed-typeof*>​::​*complete* is initialized with a callable object equivalent to the following:[](auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept -> void {if constexpr ([same_as](concept.same#concept:same_as "18.4.2 Concept same_­as [concept.same]")>) {*TRY-EVAL*(rcvr, *let-bind*(state, rcvr, std::forward(args)...)); } else { Tag()(std::move(rcvr), std::forward(args)...); }} [14](#14) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4073) Let sndr and env be subexpressions, and let Sndr be decltype((sndr))[.](#14.sentence-1) If[*sender-for*](exec.snd.concepts#concept:sender-for "33.9.3 Sender concepts [exec.snd.concepts]")> is false, then the expression *let-cpo*.transform_env(sndr, env) is ill-formed[.](#14.sentence-2) Otherwise, it is equal to:auto& [_, _, child] = sndr;return *JOIN-ENV*(*let-env*(child), *FWD-ENV*(env)); [15](#15) [#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4087) 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[.](#15.sentence-1) The expression connect(out_sndr, rcvr) has undefined behavior unless it creates an asynchronous operation ([[exec.async.ops]](exec.async.ops "33.3 Asynchronous operations")) that, when started: - [(15.1)](#15.1) invokes f when *set-cpo* is called with sndr's result datums, - [(15.2)](#15.2) makes its completion dependent on the completion of a sender returned by f, and - [(15.3)](#15.3) propagates the other completion operations sent by sndr[.](#15.sentence-2)