Files
cppdraft_translate/cppdraft/exec/let.md
2025-10-25 03:02:53 +03:00

11 KiB
Raw Blame History

[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]

1

#

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.

2

#

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<>{})

3

#

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.

4

#

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.

5

#

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(); };}

6

#

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();

7

#

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())).

8

#

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), {}, {}};}

9

#

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.

10

#

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.

11

#

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.

12

#

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}));

13

#

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)...); }}

14

#

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));

15

#

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.