[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 {template<class State, class Rcvr, class... Args>void *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*; template<class Sndr, class... Env>static 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 {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>(args)...); }template<class Error>void set_error(Error&& err) && noexcept { execution::set_error(std::move(*rcvr*), std::forward<Error>(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 <execution> 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<class Sndr, class... Env>
  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*<Sndr>>;auto cs = get_completion_signatures<*child-type*<Sndr>, *FWD-ENV-T*(Env)...>();auto fn = []<class... Ts>(*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]")<decay_t<Ts>, Ts> &&...)

- [(7.2)](#7.2)

[invocable](concept.invocable#concept:invocable "18.7.2 Concept invocable [concept.invocable]")<LetFn, decay_t<Ts>&...>

- [(7.3)](#7.3)

[sender](exec.snd.concepts#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]")<invoke_result_t<LetFn, decay_t<Ts>&...>>

- [(7.4)](#7.4)

sizeof...(Env) == 0 || [sender_in](exec.snd.concepts#concept:sender_in "33.9.3 Sender concepts [exec.snd.concepts]")<invoke_result_t<LetFn, decay_t<Ts>&...>, *env-t*  
...>

where *env-t* is the packdecltype(*let-cpo*.transform_env(declval<Sndr>(), declval<Env>()))[.](#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:[]<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<Sndr>(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*<Sndr>, *FWD-ENV-T*(env_of_t<Rcvr>)>[.](#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*<Tag(Args...)> denotes
the type *decayed-tuple*<Args...>[.](#9.sentence-3)

Then args_variant_t denotes
the type variant<monostate, *as-tuple*<LetSigs>...> 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*<Tag(Args...)> denotes
the type *call-result-t*<F, decay_t<Args>&...>[.](#10.sentence-1)

Then ops2_variant_t denotes
the typevariant<monostate, connect_result_t<*as-sndr2*<LetSigs>, *receiver2*<Rcvr, env_t>>...> 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*<Args...>;auto mkop2 = [&] {return connect( apply(std::move(state.fn),
 state.args.template emplace<args_t>(std::forward<Args>(args)...)), *receiver2*{rcvr, std::move(state.env)});};
start(state.ops2.template emplace<decltype(mkop2())>(*emplace-from*{mkop2}));

[13](#13)

[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/exec.tex#L4059)

*impls-for*<*decayed-typeof*<let-cpo>>​::​*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](concept.same#concept:same_as "18.4.2 Concept same_­as [concept.same]")<Tag, *decayed-typeof*<*set-cpo*>>) {*TRY-EVAL*(rcvr, *let-bind*(state, rcvr, std::forward<Args>(args)...)); } else { Tag()(std::move(rcvr), std::forward<Args>(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]")<Sndr, *decayed-typeof*<*let-cpo*>> 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)
