xref: /openbmc/sdbusplus/include/sdbusplus/async/stdexec/__detail/__let.hpp (revision 10d0b4b7d1498cfd5c3d37edea271a54d1984e41)
1 /*
2  * Copyright (c) 2021-2024 NVIDIA Corporation
3  *
4  * Licensed under the Apache License Version 2.0 with LLVM Exceptions
5  * (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  *
8  *   https://llvm.org/LICENSE.txt
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include "__execution_fwd.hpp"
19 
20 // include these after __execution_fwd.hpp
21 #include "__basic_sender.hpp"
22 #include "__diagnostics.hpp"
23 #include "__domain.hpp"
24 #include "__env.hpp"
25 #include "__meta.hpp"
26 #include "__any_receiver_ref.hpp" // IWYU pragma: keep for __any::__receiver_ref
27 #include "__schedulers.hpp"
28 #include "__sender_adaptor_closure.hpp"
29 #include "__senders.hpp"
30 #include "__submit.hpp"
31 #include "__transform_sender.hpp"
32 #include "__transform_completion_signatures.hpp"
33 #include "__variant.hpp"
34 
35 #include <exception>
36 
37 namespace stdexec {
38   //////////////////////////////////////////////////////////////////////////////
39   // [exec.let]
40   namespace __let {
41     // A dummy scheduler that is used by the metaprogramming below when the input sender doesn't
42     // have a completion scheduler.
43     struct __unknown_scheduler {
44       struct __attrs {
querystdexec::__let::__unknown_scheduler::__attrs45         static constexpr auto query(__is_scheduler_affine_t) noexcept -> bool {
46           return true;
47         }
48 
49         [[nodiscard]]
querystdexec::__let::__unknown_scheduler::__attrs50         constexpr auto query(get_completion_scheduler_t<set_value_t>) const noexcept {
51           return __unknown_scheduler{};
52         }
53       };
54 
55       struct __sender {
56         using sender_concept = sender_t;
57 
58         [[nodiscard]]
get_envstdexec::__let::__unknown_scheduler::__sender59         constexpr auto get_env() const noexcept -> __attrs {
60           return {};
61         }
62       };
63 
64       [[nodiscard]]
schedulestdexec::__let::__unknown_scheduler65       auto schedule() const noexcept {
66         return __sender();
67       }
68 
69       auto operator==(const __unknown_scheduler&) const noexcept -> bool = default;
70     };
71 
72     inline constexpr auto __get_rcvr = [](auto& __op_state) noexcept -> decltype(auto) {
73       return (__op_state.__rcvr_);
74     };
75 
76     inline constexpr auto __get_env = [](auto& __op_state) noexcept -> decltype(auto) {
77       return __op_state.__state_.__get_env(__op_state.__rcvr_);
78     };
79 
80     template <class _Set, class _Domain = dependent_domain>
81     struct __let_t;
82 
83     template <class _Set>
84     inline constexpr __mstring __in_which_let_msg{"In stdexec::let_value(Sender, Function)..."};
85 
86     template <>
87     inline constexpr __mstring __in_which_let_msg<set_error_t>{
88       "In stdexec::let_error(Sender, Function)..."};
89 
90     template <>
91     inline constexpr __mstring __in_which_let_msg<set_stopped_t>{
92       "In stdexec::let_stopped(Sender, Function)..."};
93 
94     template <class _Set>
95     using __on_not_callable = __callable_error<__in_which_let_msg<_Set>>;
96 
97     template <class _ReceiverId, class _SchedulerId>
98     struct __rcvr_sch {
99       using _Receiver = stdexec::__t<_ReceiverId>;
100       using _Scheduler = stdexec::__t<_SchedulerId>;
101 
102       struct __t {
103         using receiver_concept = receiver_t;
104         using __id = __rcvr_sch;
105         _Receiver __rcvr_;
106         _Scheduler __sched_;
107 
108         template <class... _As>
set_valuestdexec::__let::__rcvr_sch::__t109         void set_value(_As&&... __as) noexcept {
110           stdexec::set_value(static_cast<_Receiver&&>(__rcvr_), static_cast<_As&&>(__as)...);
111         }
112 
113         template <class _Error>
set_errorstdexec::__let::__rcvr_sch::__t114         void set_error(_Error&& __err) noexcept {
115           stdexec::set_error(static_cast<_Receiver&&>(__rcvr_), static_cast<_Error&&>(__err));
116         }
117 
set_stoppedstdexec::__let::__rcvr_sch::__t118         void set_stopped() noexcept {
119           stdexec::set_stopped(static_cast<_Receiver&&>(__rcvr_));
120         }
121 
get_envstdexec::__let::__rcvr_sch::__t122         auto get_env() const noexcept {
123           return __env::__join(__sched_env{__sched_}, stdexec::get_env(__rcvr_));
124         }
125       };
126     };
127 
128     template <class _Receiver, class _Scheduler>
129     using __receiver_with_sched_t = __t<__rcvr_sch<__id<_Receiver>, __id<_Scheduler>>>;
130 
131     // If the input sender knows its completion scheduler, make it the current scheduler
132     // in the environment seen by the result sender.
133     template <class _Scheduler, class _Env>
134     using __result_env_t = __if_c<
135       __is_scheduler_affine<schedule_result_t<_Scheduler>>,
136       _Env,
137       __join_env_t<__sched_env<_Scheduler>, _Env>
138     >;
139 
140     template <__mstring _Where, __mstring _What>
141     struct _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_ { };
142 
143 #if STDEXEC_EDG()
144     template <class _Sender, class _Set, class... _Env>
145     struct __bad_result_sender_ {
146       using __t = __mexception<
147         _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_<
148           __in_which_let_msg<_Set>,
149           "The function must return a valid sender for the current environment"_mstr
150         >,
151         _WITH_SENDER_<_Sender>,
152         _WITH_ENVIRONMENT_<_Env>...
153       >;
154     };
155     template <class _Sender, class _Set, class... _Env>
156     using __bad_result_sender = __t<__bad_result_sender_<_Sender, _Set, _Env...>>;
157 #else
158     template <class _Sender, class _Set, class... _Env>
159     using __bad_result_sender = __mexception<
160       _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_<
161         __in_which_let_msg<_Set>,
162         "The function must return a valid sender for the current environment"_mstr
163       >,
164       _WITH_SENDER_<_Sender>,
165       _WITH_ENVIRONMENT_<_Env>...
166     >;
167 #endif
168 
169     template <class _Sender, class... _Env>
170     concept __potentially_valid_sender_in = sender_in<_Sender, _Env...>
171                                          || (sender<_Sender> && (sizeof...(_Env) == 0));
172 
173     template <class _Set, class _Sender, class... _Env>
174     using __ensure_sender = __minvoke_if_c<
175       __potentially_valid_sender_in<_Sender, _Env...>,
176       __q<__midentity>,
177       __mbind_back_q<__bad_result_sender, _Set, _Env...>,
178       _Sender
179     >;
180 
181     // A metafunction that computes the result sender type for a given set of argument types
182     template <class _Set, class _Fun, class _Sched, class... _Env>
183     struct __result_sender_fn {
184       template <class... _Args>
185       using __f = __meval<
186         __ensure_sender,
187         _Set,
188         __mcall<__mtry_catch_q<__call_result_t, __on_not_callable<_Set>>, _Fun, __decay_t<_Args>&...>,
189         __result_env_t<_Sched, _Env>...
190       >;
191     };
192 
193     // The receiver that gets connected to the result sender is the input receiver,
194     // possibly augmented with the input sender's completion scheduler (which is
195     // where the result sender will be started).
196     template <class _Receiver, class _Scheduler>
197     using __result_receiver_t = __if_c<
198       __is_scheduler_affine<schedule_result_t<_Scheduler>>,
199       _Receiver,
200       __receiver_with_sched_t<_Receiver, _Scheduler>
201     >;
202 
203     template <class _ResultSender, class _Scheduler, class... _Env>
204     using __receiver_ref_t = __meval<
205       __any_::__receiver_ref,
206       __completion_signatures_of_t<_ResultSender, __result_env_t<_Scheduler, _Env>...>,
207       __result_env_t<_Scheduler, _Env>...
208     >;
209 
210     template <class _ResultSender, class _Scheduler, class _Receiver>
211     concept __needs_receiver_ref =
212       __nothrow_connectable<
213         _ResultSender,
214         __receiver_ref_t<_ResultSender, _Scheduler, env_of_t<_Receiver>>
215       >
216       && !__nothrow_connectable<_ResultSender, __result_receiver_t<_Receiver, _Scheduler>>;
217 
218     template <class _Sender, class _Receiver>
219     using __nothrow_connectable_t = __mbool<__nothrow_connectable<_Sender, _Receiver>>;
220 
221     template <class _ResultSender, class _Scheduler, class... _Env>
222     using __nothrow_connectable_receiver_ref_t = __meval<
223       __nothrow_connectable_t,
224       _ResultSender,
225       __receiver_ref_t<_ResultSender, _Scheduler, _Env...>
226     >;
227 
228     template <class _ResultSender, class _Scheduler, class _Receiver>
229     using __checked_result_receiver_t = __if_c<
230       __needs_receiver_ref<_ResultSender, _Scheduler, _Receiver>,
231       __receiver_ref_t<_ResultSender, _Scheduler, env_of_t<_Receiver>>,
232       __result_receiver_t<_Receiver, _Scheduler>
233     >;
234 
235     template <class _ResultSender, class _Scheduler, class _Receiver>
236     using __submit_result = submit_result<
237       _ResultSender,
238       __checked_result_receiver_t<_ResultSender, _Scheduler, _Receiver>
239     >;
240 
241     template <class _SetTag, class _Fun, class _Sched, class... _Env>
242     struct __transform_signal_fn {
243       template <class... _Args>
244       using __nothrow_connect = __mand<
245         __mbool<(__nothrow_decay_copyable<_Args> && ...) && __nothrow_callable<_Fun, _Args...>>,
246         __nothrow_connectable_receiver_ref_t<
247           __mcall<__result_sender_fn<_SetTag, _Fun, _Sched, _Env...>, _Args...>,
248           _Sched,
249           _Env...
250         >
251       >;
252 
253       template <class... _Args>
254       using __f = __mcall<
255         __mtry_q<__concat_completion_signatures>,
256         __completion_signatures_of_t<
257           __mcall<__result_sender_fn<_SetTag, _Fun, _Sched, _Env...>, _Args...>,
258           __result_env_t<_Sched, _Env>...
259         >,
260         __eptr_completion_if_t<__nothrow_connect<_Args...>>
261       >;
262     };
263 
264     template <class _Sender, class _Set>
265     using __completion_sched =
266       __query_result_or_t<get_completion_scheduler_t<_Set>, env_of_t<_Sender>, __unknown_scheduler>;
267 
268     template <class _LetTag, class _Fun, class _CvrefSender, class... _Env>
269     using __completions = __gather_completion_signatures<
270       __completion_signatures_of_t<_CvrefSender, _Env...>,
271       __t<_LetTag>,
272       __transform_signal_fn<
273         __t<_LetTag>,
274         _Fun,
275         __completion_sched<_CvrefSender, __t<_LetTag>>,
276         _Env...
277       >::template __f,
278       __sigs::__default_completion,
279       __mtry_q<__concat_completion_signatures>::__f
280     >;
281 
282     template <__mstring _Where, __mstring _What>
283     struct _NO_COMMON_DOMAIN_ { };
284 
285     template <class _Set>
286     using __no_common_domain_t = _NO_COMMON_DOMAIN_<
287       __in_which_let_msg<_Set>,
288       "The senders returned by Function do not all share a common domain"_mstr
289     >;
290 
291     template <class _Set, class _Sched>
292     struct __try_common_domain_fn {
293       struct __error_fn {
294         template <class... _Senders>
295         using __f = __mexception<__no_common_domain_t<_Set>, _WITH_SENDERS_<_Senders...>>;
296       };
297 
298       // If a sender is "scheduler affine", then it will complete on the same execution
299       // context on which it was started (e.g., just(42)). In this case, the domain of the
300       // scheduler is the domain of the sender.
301       template <class... _Senders>
302       using __common_domain = __common_domain_t<
303         __if_c<__is_scheduler_affine<_Senders>, schedule_result_t<_Sched>, _Senders>...
304       >;
305 
306       template <class... _Senders>
307       using __f = __mcall<__mtry_catch_q<__common_domain, __error_fn>, _Senders...>;
308     };
309 
310     // Compute all the domains of all the result senders and make sure they're all the same
311     template <class _Set, class _Child, class _Fun, class _Sched, class... _Env>
312     using __result_domain_t = __gather_completions<
313       _Set,
314       __completion_signatures_of_t<_Child, _Env...>,
315       __result_sender_fn<_Set, _Fun, _Sched, _Env...>,
316       __try_common_domain_fn<_Set, _Sched>
317     >;
318 
319     template <class _LetTag, class _Env>
__mk_transform_env_fn(_Env && __env)320     auto __mk_transform_env_fn(_Env&& __env) noexcept {
321       using _Set = __t<_LetTag>;
322       return [&]<class _Fun, class _Child>(__ignore, _Fun&&, _Child&& __child) -> decltype(auto) {
323         using __completions_t = __completion_signatures_of_t<_Child, _Env>;
324         if constexpr (__merror<__completions_t>) {
325           return __completions_t();
326         } else {
327           using _Scheduler = __completion_sched<_Child, _Set>;
328           if constexpr (__is_scheduler_affine<schedule_result_t<_Scheduler>>) {
329             return (__env);
330           } else {
331             return __env::__join(
332               __sched_env{get_completion_scheduler<_Set>(stdexec::get_env(__child))},
333               static_cast<_Env&&>(__env));
334           }
335         }
336       };
337     }
338 
339     template <class _LetTag, class _Env>
__mk_transform_sender_fn(_Env &&)340     auto __mk_transform_sender_fn(_Env&&) noexcept {
341       using _Set = __t<_LetTag>;
342 
343       return []<class _Fun, class _Child>(__ignore, _Fun&& __fun, _Child&& __child) {
344         using __completions_t = __completion_signatures_of_t<_Child, _Env>;
345 
346         if constexpr (__merror<__completions_t>) {
347           return __completions_t();
348         } else {
349           using _Sched = __completion_sched<_Child, _Set>;
350           using _Domain = __result_domain_t<_Set, _Child, _Fun, _Sched, _Env>;
351 
352           if constexpr (__merror<_Domain>) {
353             return _Domain();
354           } else if constexpr (same_as<_Domain, dependent_domain>) {
355             using _Domain2 = __late_domain_of_t<_Child, _Env>;
356             return __make_sexpr<__let_t<_Set, _Domain2>>(
357               static_cast<_Fun&&>(__fun), static_cast<_Child&&>(__child));
358           } else {
359             static_assert(!same_as<_Domain, __unknown_scheduler>);
360             return __make_sexpr<__let_t<_Set, _Domain>>(
361               static_cast<_Fun&&>(__fun), static_cast<_Child&&>(__child));
362           }
363         }
364       };
365     }
366 
367     //! Metafunction creating the operation state needed to connect the result of calling
368     //! the sender factory function, `_Fun`, and passing its result to a receiver.
369     template <class _Receiver, class _Fun, class _Set, class _Sched>
370     struct __submit_datum_for {
371       // compute the result of calling submit with the result of executing _Fun
372       // with _Args. if the result is void, substitute with __ignore.
373       template <class... _Args>
374       using __f = __submit_result<
375         __mcall<__result_sender_fn<_Set, _Fun, _Sched, env_of_t<_Receiver>>, _Args...>,
376         _Sched,
377         _Receiver
378       >;
379     };
380 
381     //! The core of the operation state for `let_*`.
382     //! This gets bundled up into a larger operation state (`__detail::__op_state<...>`).
383     template <class _Receiver, class _Fun, class _Set, class _Sched, class... _Tuples>
384     struct __let_state {
385       using __fun_t = _Fun;
386       using __sched_t = _Sched;
387       using __env_t = __result_env_t<_Sched, env_of_t<_Receiver>>;
388       using __rcvr_t = __receiver_with_sched_t<_Receiver, _Sched>;
389       using __result_variant = __variant_for<__monostate, _Tuples...>;
390       using __submit_variant = __variant_for<
391         __monostate,
392         __mapply<__submit_datum_for<_Receiver, _Fun, _Set, _Sched>, _Tuples>...
393       >;
394 
395       template <class _ResultSender, class _OpState>
__get_result_receiverstdexec::__let::__let_state396       auto __get_result_receiver(const _ResultSender&, _OpState& __op_state) -> decltype(auto) {
397         if constexpr (__needs_receiver_ref<_ResultSender, _Sched, _Receiver>) {
398           using __receiver_ref = __receiver_ref_t<_ResultSender, _Sched, env_of_t<_Receiver>>;
399           return __receiver_ref{__op_state, __let::__get_env, __let::__get_rcvr};
400         } else {
401           _Receiver& __rcvr = __op_state.__rcvr_;
402           if constexpr (__is_scheduler_affine<schedule_result_t<_Sched>>) {
403             return static_cast<_Receiver&&>(__rcvr);
404           } else {
405             return __rcvr_t{static_cast<_Receiver&&>(__rcvr), this->__sched_};
406           }
407         }
408       }
409 
__get_envstdexec::__let::__let_state410       auto __get_env(const _Receiver& __rcvr) const noexcept -> __env_t {
411         if constexpr (__is_scheduler_affine<schedule_result_t<_Sched>>) {
412           return stdexec::get_env(__rcvr);
413         } else {
414           return __env::__join(__sched_env{__sched_}, stdexec::get_env(__rcvr));
415         }
416       }
417 
418       STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
419       _Fun __fun_;
420       STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
421       _Sched __sched_;
422       //! Variant to hold the results passed from upstream before passing them to the function:
423       __result_variant __args_{};
424       //! Variant type for holding the operation state from connecting
425       //! the function result to the downstream receiver:
426       __submit_variant __storage_{};
427     };
428 
429     //! Implementation of the `let_*_t` types, where `_Set` is, e.g., `set_value_t` for `let_value`.
430     template <class _Set, class _Domain>
431     struct __let_t {
432       using __domain_t = _Domain;
433       using __t = _Set;
434 
435       template <sender _Sender, __movable_value _Fun>
operator ()stdexec::__let::__let_t436       auto operator()(_Sender&& __sndr, _Fun __fun) const -> __well_formed_sender auto {
437         auto __domain = __get_early_domain(__sndr);
438         return stdexec::transform_sender(
439           __domain,
440           __make_sexpr<__let_t<_Set>>(static_cast<_Fun&&>(__fun), static_cast<_Sender&&>(__sndr)));
441       }
442 
443       template <class _Fun>
STDEXEC_ATTRIBUTEstdexec::__let::__let_t444       STDEXEC_ATTRIBUTE(always_inline)
445       auto operator()(_Fun __fun) const -> __binder_back<__let_t, _Fun> {
446         return {{static_cast<_Fun&&>(__fun)}, {}, {}};
447       }
448 
449       template <sender_expr_for<__let_t<_Set>> _Sender, class _Env>
transform_envstdexec::__let::__let_t450       static auto transform_env(_Sender&& __sndr, const _Env& __env) -> decltype(auto) {
451         return __sexpr_apply(
452           static_cast<_Sender&&>(__sndr), __mk_transform_env_fn<__let_t<_Set>>(__env));
453       }
454 
455       template <sender_expr_for<__let_t<_Set>> _Sender, class _Env>
456         requires same_as<__early_domain_of_t<_Sender>, dependent_domain>
transform_senderstdexec::__let::__let_t457       static auto transform_sender(_Sender&& __sndr, const _Env& __env) -> decltype(auto) {
458         return __sexpr_apply(
459           static_cast<_Sender&&>(__sndr), __mk_transform_sender_fn<__let_t<_Set>>(__env));
460       }
461     };
462 
463     template <class _Set, class _Domain>
464     struct __let_impl : __sexpr_defaults {
465       static constexpr auto get_attrs =
466         []<class _Fun, class _Child>(const _Fun&, const _Child& __child) noexcept {
467           if constexpr (!same_as<_Domain, dependent_domain>) {
468             return __env::__join(prop{get_domain, _Domain()}, stdexec::get_env(__child));
469           } else {
470             using _Sched = __completion_sched<_Child, _Set>;
471             using _Domain2 = __result_domain_t<_Set, _Child, _Fun, _Sched>;
472 
473             if constexpr (__merror<_Domain2>) {
474               return __env::__join(prop{get_domain, dependent_domain()}, stdexec::get_env(__child));
475             } else {
476               return __env::__join(prop{get_domain, _Domain2()}, stdexec::get_env(__child));
477             }
478           }
479         };
480 
481       static constexpr auto get_completion_signatures =
482         []<class _Self, class... _Env>(_Self&&, _Env&&...) noexcept
483         -> __completions<__let_t<_Set, _Domain>, __data_of<_Self>, __child_of<_Self>, _Env...> {
484         static_assert(sender_expr_for<_Self, __let_t<_Set, _Domain>>);
485         return {};
486       };
487 
488       static constexpr auto get_state =
489         []<class _Sender, class _Receiver>(_Sender&& __sndr, _Receiver&) {
490           static_assert(sender_expr_for<_Sender, __let_t<_Set, _Domain>>);
491           using _Fun = __data_of<_Sender>;
492           using _Child = __child_of<_Sender>;
493           using _Sched = __decay_t<__completion_sched<_Child, _Set>>;
494           using __mk_let_state = __mbind_front_q<__let_state, _Receiver, _Fun, _Set, _Sched>;
495 
496           using __let_state_t = __gather_completions_of<
497             _Set,
498             _Child,
499             env_of_t<_Receiver>,
500             __q<__decayed_tuple>,
501             __mk_let_state
502           >;
503 
504           return __sndr.apply(
505             static_cast<_Sender&&>(__sndr),
506             [&]<class _Fn, class _Child>(__ignore, _Fn&& __fn, _Child&& __child) {
507               _Sched __sched = query_or(
508                 get_completion_scheduler<_Set>, stdexec::get_env(__child), __unknown_scheduler());
509               return __let_state_t{static_cast<_Fn&&>(__fn), __sched};
510             });
511         };
512 
513       //! Helper function to actually invoke the function to produce `let_*`'s sender,
514       //! connect it to the downstream receiver, and start it. This is the heart of
515       //! `let_*`.
516       template <class _State, class _OpState, class... _As>
__bind_stdexec::__let::__let_impl517       static void __bind_(_State& __state, _OpState& __op_state, _As&&... __as) {
518         // Store the passed-in (received) args:
519         auto& __args = __state.__args_.emplace_from(__tup::__mktuple, static_cast<_As&&>(__as)...);
520         // Apply the function to the args to get the sender:
521         auto __sndr2 = __args.apply(std::move(__state.__fun_), __args);
522         // Create a receiver based on the state, the computed sender, and the operation state:
523         auto __rcvr2 = __state.__get_result_receiver(__sndr2, __op_state);
524         // Connect the sender to the receiver and start it:
525         using __result_t = decltype(submit_result{std::move(__sndr2), std::move(__rcvr2)});
526         auto& __op = __state.__storage_
527                        .template emplace<__result_t>(std::move(__sndr2), std::move(__rcvr2));
528         __op.submit(std::move(__sndr2), std::move(__rcvr2));
529       }
530 
531       template <class _OpState, class... _As>
__bindstdexec::__let::__let_impl532       static void __bind(_OpState& __op_state, _As&&... __as) noexcept {
533         using _State = decltype(__op_state.__state_);
534         using _Receiver = decltype(__op_state.__rcvr_);
535         using _Fun = _State::__fun_t;
536         using _Sched = _State::__sched_t;
537         using _ResultSender =
538           __mcall<__result_sender_fn<_Set, _Fun, _Sched, env_of_t<_Receiver>>, _As...>;
539 
540         _State& __state = __op_state.__state_;
541         _Receiver& __rcvr = __op_state.__rcvr_;
542 
543         if constexpr (
544           (__nothrow_decay_copyable<_As> && ...) && __nothrow_callable<_Fun, _As...>
545           && __v<__nothrow_connectable_receiver_ref_t<_ResultSender, _Sched, env_of_t<_Receiver>>>) {
546           __bind_(__state, __op_state, static_cast<_As&&>(__as)...);
547         } else {
548           STDEXEC_TRY {
549             __bind_(__state, __op_state, static_cast<_As&&>(__as)...);
550           }
551           STDEXEC_CATCH_ALL {
552             using _Receiver = decltype(__op_state.__rcvr_);
553             stdexec::set_error(static_cast<_Receiver&&>(__rcvr), std::current_exception());
554           }
555         }
556       }
557 
558       static constexpr auto complete = []<class _OpState, class _Tag, class... _As>(
559                                          __ignore,
560                                          _OpState& __op_state,
561                                          _Tag,
562                                          _As&&... __as) noexcept -> void {
563         if constexpr (__same_as<_Tag, _Set>) {
564           // Intercept the channel of interest to compute the sender and connect it:
565           __bind(__op_state, static_cast<_As&&>(__as)...);
566         } else {
567           // Forward the other channels downstream:
568           using _Receiver = decltype(__op_state.__rcvr_);
569           _Tag()(static_cast<_Receiver&&>(__op_state.__rcvr_), static_cast<_As&&>(__as)...);
570         }
571       };
572     };
573   } // namespace __let
574 
575   using let_value_t = __let::__let_t<set_value_t>;
576   inline constexpr let_value_t let_value{};
577 
578   using let_error_t = __let::__let_t<set_error_t>;
579   inline constexpr let_error_t let_error{};
580 
581   using let_stopped_t = __let::__let_t<set_stopped_t>;
582   inline constexpr let_stopped_t let_stopped{};
583 
584   template <class _Set, class _Domain>
585   struct __sexpr_impl<__let::__let_t<_Set, _Domain>> : __let::__let_impl<_Set, _Domain> { };
586 } // namespace stdexec
587