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 be set_value, set_error, and set_stopped, respectively.
Let the expression let-cpo be one of let_value, let_error, or let_stopped.
Let let-tag denote a unique, empty class type for each of let_value, let_error, and let_stopped.
For subexpressions sndr and env, let let-env(sndr, env) be expression-equivalent to the first well-formed expression below:
  • SCHED-ENV(get_completion_scheduler<decayed-typeof<set-cpo>>(get_env(sndr),
    FWD-ENV(env)))
  • MAKE-ENV(get_domain, get_completion_domain<decayed-typeof<set-cpo>>(get_env(sndr),
    FWD-ENV(env)))
  • (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 make-sender(let-cpo, f, sndr).
Let let-data denote the following exposition-only class template: template<class Sndr, class Fn> struct let-data { Sndr sndr; // exposition only Fn fn; // exposition only };
Then let the expression let-cpo.transform_sender(s, es...) be expression-equivalent to: make-sender(let-tag{}, let-data{s.template get<2>(), s.template get<1>()}) except that s is evaluated only once.
The exposition-only class template impls-for ([exec.snd.expos]) is specialized for let-tag as follows: namespace std::execution { template<> struct impls-for<let-tag> : default-impls { static constexpr auto get-state = see below; static constexpr auto start = 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_tag; 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
  • decltype(e) models queryable and
  • given a query object q and a pack of subexpressions args, the expression e.query(q, args...) is expression-equivalent to env.query(q, args...) if that expression is valid; otherwise, if the type of q satisfies forwarding-query, e.query(q, args...) is expression-equivalent to get_env(rcvr).query
    (q, args...)
    ; otherwise, e.query(q, args...) is ill-formed.
template<class Sndr, class... Env> static consteval void check-types();
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 is-valid-let-sender is true if and only if all of the following are true: where env-t is the pack decltype(JOIN-ENV(let-env(declval<child-type<Sndr>>(), declval<​Env>()), FWD-ENV(declval<Env>()))).
Let let-state denote the following exposition-only class template: template<class Cpo, class Sndr, class Fn, class Rcvr, class ArgsVariant, class OpsVariant> struct let-state { using env_t = decltype(let-env(declval<Sndr>()), get_env(declval<Rcvr&>())); // exposition only Fn fn; // exposition only env_t env; // exposition only ArgsVariant args; // exposition only OpsVariant ops; // exposition only template<class Tag, class... Ts> constexpr void impl(Rcvr& rcvr, Tag tag, Ts&&... ts) noexcept // exposition only { using args_t = decayed-tuple<Ts...>; using receiver_type = receiver2<Rcvr, env_t>; using sender_type = apply_result_t<Fn, args_t&>; if constexpr (is_same_v<Tag, Cpo>) { try { auto& tuple = args.template emplace<args_t>(std::forward<Ts>(ts)...); ops.template emplace<monostate>(); auto&& sndr = apply(std::move(fn), tuple); using op_t = connect_result_t<sender_type, receiver_type>; auto mkop2 = [&] { return connect(std::forward<sender_type>(sndr), receiver_type{rcvr, env}); }; auto& op = ops.template emplace<op_t>(emplace-from{mkop2}); start(op); } catch (...) { constexpr bool nothrow = is_nothrow_constructible_v<args_t, Ts...> && is_nothrow_applicable_v<Fn, args_t&> && noexcept(connect(declval<sender_type>(), receiver_type{rcvr, env})); if constexpr (!nothrow) { set_error(std::move(rcvr), current_exception()); } } } else { tag(std::move(rcvr), std::forward<Ts>(ts)...); } } struct receiver { // exposition only let-state& state; // exposition only Rcvr& rcvr; // exposition only using receiver_concept = receiver_tag; template<class... Args> constexpr void set_value(Args&&... args) noexcept { state.impl(rcvr, execution::set_value, std::forward<Args>(args)...); } template<class... Args> constexpr void set_error(Args&&... args) noexcept { state.impl(rcvr, execution::set_error, std::forward<Args>(args)...); } template<class... Args> constexpr void set_stopped(Args&&... args) noexcept { state.impl(rcvr, execution::set_stopped, std::forward<Args>(args)...); } constexpr env_of_t<const Rcvr&> get_env() const noexcept { return execution::get_env(rcvr); } }; using op_t = connect_result_t<Sndr, receiver>; // exposition only constexpr let-state(Sndr&& sndr, Fn fn, Rcvr& rcvr) // exposition only : fn(std::move(fn)), env(let-env(sndr), get_env(rcvr)), ops(in_place_type<op_t>, std::forward<Sndr>(sndr), receiver{*this, rcvr}) {} };
impls-for<let-tag>​::​get-state is initialized with a callable object equivalent to the following: []<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) requires see below { auto& [_, data] = sndr; auto& [child, fn] = data; using child_t = decltype(std::forward_like<Sndr>(child)); using fn_t = decay_t<decltype(fn)>; using args_variant_t = see below; using ops_variant_t = see below; using state_t = let-state<decayed-typeof<set-cpo>, child_t, fn_t, Rcvr, args_variant_t, ops_variant_t>; return state_t(std::forward_like<Sndr>(child), std::forward_like<Sndr>(fn), rcvr); }
Let Sigs be a pack of the arguments to the completion_signatures specialization named by completion_signatures_of_t<child-type<Sndr>, FWD-ENV-T(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<F, decay_t<Args>&...>.
Then ops_variant_t denotes the type variant<monostate, connect_result_t<child_t, let-state::receiver>, connect_result_t<as-sndr2<LetSigs>, 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.
impls-for<let-tag>​::​start is initialized with a callable object equivalent to the following: []<class State, class Rcvr>(State& state, Rcvr&) noexcept { start(get<typename State::op_t>(state.ops)); }
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.