33 Execution control library [exec]

33.9 Senders [exec.snd]

33.9.12 Sender adaptors [exec.adapt]

33.9.12.8 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 be set_value, set_error, and set_stopped, respectively.
Let the expression let-cpo be one of let_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), empty_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.general]) 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 only template<> struct impls-for<decayed-typeof<let-cpo>> : default-impls { static constexpr auto get-state = see below; static constexpr auto complete = see below; }; }
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 JOIN-ENV(env, FWD-ENV(execution::get_env(rcvr))); } Rcvr& rcvr; // exposition only Env env; // exposition only }; }
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{std::forward_like<Sndr>(fn), let-env(child), {}, {}}; }
Let Sigs be a pack of the arguments to the completion_signatures specialization named by completion_signatures_of_t<child-type<Sndr>, env_of_t<Rcvr>>.
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<LetSigs>...> 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<Fn, decay_t<Args>&...>.
Then ops2_variant_t denotes the type variant<monostate, connect_result_t<as-sndr2<LetSigs>, receiver2<Rcvr, Env>>...> 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>(args)...)), receiver2{rcvr, std::move(state.env)}); }; start(state.ops2.template emplace<decltype(mkop2())>(emplace-from{mkop2}));
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<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)...); } }
Let sndr and env be subexpressions, and let Sndr be decltype((sndr)).
If sender-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 JOIN-ENV(let-env(sndr), 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.