xref: /openbmc/sdbusplus/include/sdbusplus/async/stdexec/__detail/__let.hpp (revision 6269157344064457b7e1241d4efe59c6c51c7a59)
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 "__inline_scheduler.hpp"
26 #include "__meta.hpp"
27 #include "__receiver_ref.hpp"
28 #include "__sender_adaptor_closure.hpp"
29 #include "__senders.hpp"
30 #include "__tag_invoke.hpp"
31 #include "__transform_completion_signatures.hpp"
32 #include "__transform_sender.hpp"
33 #include "__variant.hpp"
34 
35 #include <exception>
36 
37 namespace stdexec
38 {
39 //////////////////////////////////////////////////////////////////////////////
40 // [exec.let]
41 namespace __let
42 {
43 // A dummy scheduler that is used by the metaprogramming below when the input
44 // sender doesn't have a completion scheduler.
45 struct __unknown_scheduler
46 {
47     struct __env
48     {
querystdexec::__let::__unknown_scheduler::__env49         static constexpr bool query(__is_scheduler_affine_t) noexcept
50         {
51             return true;
52         }
53 
querystdexec::__let::__unknown_scheduler::__env54         constexpr auto query(
55             get_completion_scheduler_t<set_value_t>) const noexcept
56         {
57             return __unknown_scheduler{};
58         }
59     };
60 
61     struct __sender
62     {
63         using sender_concept = sender_t;
64 
get_envstdexec::__let::__unknown_scheduler::__sender65         constexpr auto get_env() const noexcept -> __env
66         {
67             return __env();
68         }
69     };
70 
schedulestdexec::__let::__unknown_scheduler71     auto schedule() const noexcept
72     {
73         return __sender();
74     }
75 
76     bool operator==(const __unknown_scheduler&) const noexcept = default;
77 };
78 
79 inline constexpr auto __get_rcvr =
80     [](auto& __op_state) noexcept -> decltype(auto) {
81     return (__op_state.__rcvr_);
82 };
83 
84 inline constexpr auto __get_env =
85     [](auto& __op_state) noexcept -> decltype(auto) {
86     return __op_state.__state_.__get_env(__op_state.__rcvr_);
87 };
88 
89 template <class _Set, class _Domain = dependent_domain>
90 struct __let_t;
91 
92 template <class _Set>
93 inline constexpr __mstring __in_which_let_msg{
94     "In stdexec::let_value(Sender, Function)..."};
95 
96 template <>
97 inline constexpr __mstring __in_which_let_msg<set_error_t>{
98     "In stdexec::let_error(Sender, Function)..."};
99 
100 template <>
101 inline constexpr __mstring __in_which_let_msg<set_stopped_t>{
102     "In stdexec::let_stopped(Sender, Function)..."};
103 
104 template <class _Set>
105 using __on_not_callable = __callable_error<__in_which_let_msg<_Set>>;
106 
107 template <class _Receiver, class _Scheduler>
108 struct __receiver_with_sched
109 {
110     using receiver_concept = receiver_t;
111     _Receiver __rcvr_;
112     _Scheduler __sched_;
113 
114     template <class... _As>
set_valuestdexec::__let::__receiver_with_sched115     void set_value(_As&&... __as) noexcept
116     {
117         stdexec::set_value(static_cast<_Receiver&&>(__rcvr_),
118                            static_cast<_As&&>(__as)...);
119     }
120 
121     template <class _Error>
set_errorstdexec::__let::__receiver_with_sched122     void set_error(_Error&& __err) noexcept
123     {
124         stdexec::set_error(static_cast<_Receiver&&>(__rcvr_),
125                            static_cast<_Error&&>(__err));
126     }
127 
set_stoppedstdexec::__let::__receiver_with_sched128     void set_stopped() noexcept
129     {
130         stdexec::set_stopped(static_cast<_Receiver&&>(__rcvr_));
131     }
132 
get_envstdexec::__let::__receiver_with_sched133     auto get_env() const noexcept
134     {
135         return __env::__join(
136             prop{get_scheduler, __sched_},
137             __env::__without(stdexec::get_env(__rcvr_), get_domain));
138     }
139 };
140 
141 template <class _Receiver, class _Scheduler>
142 __receiver_with_sched(_Receiver, _Scheduler)
143     -> __receiver_with_sched<_Receiver, _Scheduler>;
144 
145 // If the input sender knows its completion scheduler, make it the current
146 // scheduler in the environment seen by the result sender.
147 template <class _Scheduler, class _Env>
148 using __result_env_t =
149     __if_c<__is_scheduler_affine<schedule_result_t<_Scheduler>>, _Env,
150            __env::__join_t< //
151                prop<get_scheduler_t, _Scheduler>,
152                __env::__without_t<_Env, get_domain_t>>>;
153 
154 template <__mstring _Where, __mstring _What>
155 struct _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_
156 {};
157 
158 #if STDEXEC_EDG()
159 template <class _Sender, class _Set, class... _Env>
160 struct __bad_result_sender_
161 {
162     using __t = __mexception<
163         _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_<
164             __in_which_let_msg<_Set>,
165             "The function must return a valid sender for the current environment"_mstr>,
166         _WITH_SENDER_<_Sender>, _WITH_ENVIRONMENT_<_Env>...>;
167 };
168 template <class _Sender, class _Set, class... _Env>
169 using __bad_result_sender = __t<__bad_result_sender_<_Sender, _Set, _Env...>>;
170 #else
171 template <class _Sender, class _Set, class... _Env>
172 using __bad_result_sender = __mexception<
173     _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_<
174         __in_which_let_msg<_Set>,
175         "The function must return a valid sender for the current environment"_mstr>,
176     _WITH_SENDER_<_Sender>, _WITH_ENVIRONMENT_<_Env>...>;
177 #endif
178 
179 template <class _Sender, class... _Env>
180 concept __potentially_valid_sender_in =
181     sender_in<_Sender, _Env...> || (sender<_Sender> && (sizeof...(_Env) == 0));
182 
183 template <class _Set, class _Sender, class... _Env>
184 using __ensure_sender = //
185     __minvoke_if_c<__potentially_valid_sender_in<_Sender, _Env...>,
186                    __q<__midentity>,
187                    __mbind_back_q<__bad_result_sender, _Set, _Env...>, _Sender>;
188 
189 // A metafunction that computes the result sender type for a given set of
190 // argument types
191 template <class _Set, class _Fun, class _Sched, class... _Env>
192 struct __result_sender_fn
193 {
194     template <class... _Args>
195     using __f = //
196         __meval<
197             __ensure_sender, _Set,
198             __mcall<__mtry_catch_q<__call_result_t, __on_not_callable<_Set>>,
199                     _Fun, __decay_t<_Args>&...>,
200             __result_env_t<_Sched, _Env>...>;
201 };
202 
203 // The receiver that gets connected to the result sender is the input receiver,
204 // possibly augmented with the input sender's completion scheduler (which is
205 // where the result sender will be started).
206 template <class _Receiver, class _Scheduler>
207 using __result_receiver_t =
208     __if_c<__is_scheduler_affine<schedule_result_t<_Scheduler>>, _Receiver,
209            __receiver_with_sched<_Receiver, _Scheduler>>;
210 
211 template <class _ResultSender, class _Scheduler, class... _Env>
212 using __receiver_ref_t = //
213     __meval<__any_::__receiver_ref,
214             __completion_signatures_of_t<_ResultSender,
215                                          __result_env_t<_Scheduler, _Env>...>,
216             __result_env_t<_Scheduler, _Env>...>;
217 
218 template <class _ResultSender, class _Scheduler, class _Receiver>
219 concept __needs_receiver_ref =
220     __nothrow_connectable<
221         _ResultSender,
222         __receiver_ref_t<_ResultSender, _Scheduler, env_of_t<_Receiver>>> &&
223     !__nothrow_connectable<_ResultSender,
224                            __result_receiver_t<_Receiver, _Scheduler>>;
225 
226 template <class _Sender, class _Receiver>
227 using __nothrow_connectable_t =
228     __mbool<__nothrow_connectable<_Sender, _Receiver>>;
229 
230 template <class _ResultSender, class _Scheduler, class... _Env>
231 using __nothrow_connectable_receiver_ref_t =
232     __meval<__nothrow_connectable_t, _ResultSender,
233             __receiver_ref_t<_ResultSender, _Scheduler, _Env...>>;
234 
235 template <class _ResultSender, class _Scheduler, class _Receiver>
236 using __checked_result_receiver_t = //
237     __if_c<__needs_receiver_ref<_ResultSender, _Scheduler, _Receiver>,
238            __receiver_ref_t<_ResultSender, _Scheduler, env_of_t<_Receiver>>,
239            __result_receiver_t<_Receiver, _Scheduler>>;
240 
241 template <class _ResultSender, class _Scheduler, class _Receiver>
242 using __op_state_t = connect_result_t<
243     _ResultSender,
244     __checked_result_receiver_t<_ResultSender, _Scheduler, _Receiver>>;
245 
246 template <class _SetTag, class _Fun, class _Sched, class... _Env>
247 struct __transform_signal_fn
248 {
249     template <class... _Args>
250     using __nothrow_connect =
251         __mand<__mbool<(__nothrow_decay_copyable<_Args> && ...) &&
252                        __nothrow_callable<_Fun, _Args...>>,
253                __nothrow_connectable_receiver_ref_t<
254                    __mcall<__result_sender_fn<_SetTag, _Fun, _Sched, _Env...>,
255                            _Args...>,
256                    _Sched, _Env...>>;
257 
258     template <class... _Args>
259     using __f = //
260         __mcall<__mtry_q<__concat_completion_signatures>,
261                 __completion_signatures_of_t<
262                     __mcall<__result_sender_fn<_SetTag, _Fun, _Sched, _Env...>,
263                             _Args...>,
264                     __result_env_t<_Sched, _Env>...>,
265                 __eptr_completion_if_t<__nothrow_connect<_Args...>>>;
266 };
267 
268 template <class _Sender, class _Set>
269 using __completion_sched =
270     __query_result_or_t<get_completion_scheduler_t<_Set>, env_of_t<_Sender>,
271                         __unknown_scheduler>;
272 
273 template <class _LetTag, class _Fun, class _CvrefSender, class... _Env>
274 using __completions = //
275     __gather_completion_signatures<
276         __completion_signatures_of_t<_CvrefSender, _Env...>, __t<_LetTag>,
277         __transform_signal_fn<__t<_LetTag>, _Fun,
278                               __completion_sched<_CvrefSender, __t<_LetTag>>,
279                               _Env...>::template __f,
280         __sigs::__default_completion,
281         __mtry_q<__concat_completion_signatures>::__f>;
282 
283 template <__mstring _Where, __mstring _What>
284 struct _NO_COMMON_DOMAIN_
285 {};
286 
287 template <class _Set>
288 using __no_common_domain_t = //
289     _NO_COMMON_DOMAIN_<
290         __in_which_let_msg<_Set>,
291         "The senders returned by Function do not all share a common domain"_mstr>;
292 
293 template <class _Set>
294 struct __try_common_domain_fn
295 {
296     struct __error_fn
297     {
298         template <class... _Senders>
299         using __f = __mexception<__no_common_domain_t<_Set>,
300                                  _WITH_SENDERS_<_Senders...>>;
301     };
302 
303     template <class... _Senders>
304     using __f = __mcall<__mtry_catch_q<__domain::__common_domain_t, __error_fn>,
305                         _Senders...>;
306 };
307 
308 // Compute all the domains of all the result senders and make sure they're all
309 // the same
310 template <class _Set, class _Child, class _Fun, class _Env, class _Sched>
311 using __result_domain_t = //
312     __gather_completions<_Set, __completion_signatures_of_t<_Child, _Env>,
313                          __result_sender_fn<_Set, _Fun, _Sched, _Env>,
314                          __try_common_domain_fn<_Set>>;
315 
316 template <class _LetTag, class _Env>
__mk_transform_env_fn(_Env && __env)317 auto __mk_transform_env_fn(_Env&& __env) noexcept
318 {
319     using _Set = __t<_LetTag>;
320     return [&]<class _Fun, class _Child>(__ignore, _Fun&&,
321                                          _Child&& __child) -> decltype(auto) {
322         using __completions_t = __completion_signatures_of_t<_Child, _Env>;
323         if constexpr (__merror<__completions_t>)
324         {
325             return __completions_t();
326         }
327         else
328         {
329             using _Scheduler = __completion_sched<_Child, _Set>;
330             if constexpr (__is_scheduler_affine<schedule_result_t<_Scheduler>>)
331             {
332                 return (__env);
333             }
334             else
335             {
336                 return __env::__join(
337                     prop{get_scheduler, get_completion_scheduler<_Set>(
338                                             stdexec::get_env(__child))},
339                     __env::__without(static_cast<_Env&&>(__env), get_domain));
340             }
341         }
342     };
343 }
344 
345 template <class _LetTag, class _Env>
__mk_transform_sender_fn(_Env &&)346 auto __mk_transform_sender_fn(_Env&&) noexcept
347 {
348     using _Set = __t<_LetTag>;
349 
350     return []<class _Fun, class _Child>(__ignore, _Fun&& __fun,
351                                         _Child&& __child) {
352         using __completions_t = __completion_signatures_of_t<_Child, _Env>;
353 
354         if constexpr (__merror<__completions_t>)
355         {
356             return __completions_t();
357         }
358         else
359         {
360             using _Sched = __completion_sched<_Child, _Set>;
361             using _Domain = __result_domain_t<_Set, _Child, _Fun, _Env, _Sched>;
362 
363             if constexpr (__merror<_Domain>)
364             {
365                 return _Domain();
366             }
367             else if constexpr (same_as<_Domain, dependent_domain>)
368             {
369                 using _Domain2 = __late_domain_of_t<_Child, _Env>;
370                 return __make_sexpr<__let_t<_Set, _Domain2>>(
371                     static_cast<_Fun&&>(__fun), static_cast<_Child&&>(__child));
372             }
373             else
374             {
375                 static_assert(!same_as<_Domain, __unknown_scheduler>);
376                 return __make_sexpr<__let_t<_Set, _Domain>>(
377                     static_cast<_Fun&&>(__fun), static_cast<_Child&&>(__child));
378             }
379         }
380     };
381 }
382 
383 //! Metafunction creating the operation state needed to connect the result of
384 //! calling the sender factory function, `_Fun`, and passing its result to a
385 //! receiver.
386 template <class _Receiver, class _Fun, class _Set, class _Sched>
387 struct __op_state_for
388 {
389     template <class... _Args>
390     using __f = __op_state_t<
391         __mcall<__result_sender_fn<_Set, _Fun, _Sched, env_of_t<_Receiver>>,
392                 _Args...>,
393         _Sched, _Receiver>;
394 };
395 
396 //! The core of the operation state for `let_*`.
397 //! This gets bundled up into a larger operation state
398 //! (`__detail::__op_state<...>`).
399 template <class _Receiver, class _Fun, class _Set, class _Sched,
400           class... _Tuples>
401 struct __let_state
402 {
403     using __fun_t = _Fun;
404     using __sched_t = _Sched;
405     using __env_t = __result_env_t<_Sched, env_of_t<_Receiver>>;
406     using __result_variant = __variant_for<__monostate, _Tuples...>;
407     using __op_state_variant = //
408         __variant_for<__monostate,
409                       __mapply<__op_state_for<_Receiver, _Fun, _Set, _Sched>,
410                                _Tuples>...>;
411 
412     template <class _ResultSender, class _OpState>
__get_result_receiverstdexec::__let::__let_state413     auto __get_result_receiver(const _ResultSender&, _OpState& __op_state)
414         -> decltype(auto)
415     {
416         if constexpr (__needs_receiver_ref<_ResultSender, _Sched, _Receiver>)
417         {
418             using __receiver_ref =
419                 __receiver_ref_t<_ResultSender, _Sched, env_of_t<_Receiver>>;
420             return __receiver_ref{__op_state, __let::__get_env,
421                                   __let::__get_rcvr};
422         }
423         else
424         {
425             _Receiver& __rcvr = __op_state.__rcvr_;
426             if constexpr (__is_scheduler_affine<schedule_result_t<_Sched>>)
427             {
428                 return static_cast<_Receiver&&>(__rcvr);
429             }
430             else
431             {
432                 return __receiver_with_sched{static_cast<_Receiver&&>(__rcvr),
433                                              this->__sched_};
434             }
435         }
436     }
437 
__get_envstdexec::__let::__let_state438     auto __get_env(const _Receiver& __rcvr) const noexcept -> __env_t
439     {
440         if constexpr (__is_scheduler_affine<schedule_result_t<_Sched>>)
441         {
442             return stdexec::get_env(__rcvr);
443         }
444         else
445         {
446             return __env::__join(
447                 prop{get_scheduler, __sched_},
448                 __env::__without(stdexec::get_env(__rcvr), get_domain));
449         }
450     }
451 
452     STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
453     _Fun __fun_;
454     STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
455     _Sched __sched_;
456     //! Variant to hold the results passed from upstream before passing them to
457     //! the function:
458     __result_variant __args_{};
459     //! Variant type for holding the operation state from connecting
460     //! the function result to the downstream receiver:
461     __op_state_variant __op_state3_{};
462 };
463 
464 //! Implementation of the `let_*_t` types, where `_Set` is, e.g., `set_value_t`
465 //! for `let_value`.
466 template <class _Set, class _Domain>
467 struct __let_t
468 {
469     using __domain_t = _Domain;
470     using __t = _Set;
471 
472     template <sender _Sender, __movable_value _Fun>
operator ()stdexec::__let::__let_t473     auto operator()(_Sender&& __sndr, _Fun __fun) const -> __well_formed_sender
474         auto
475     {
476         auto __domain = __get_early_domain(__sndr);
477         return stdexec::transform_sender(
478             __domain,
479             __make_sexpr<__let_t<_Set>>(static_cast<_Fun&&>(__fun),
480                                         static_cast<_Sender&&>(__sndr)));
481     }
482 
483     template <class _Fun>
484     STDEXEC_ATTRIBUTE((always_inline))
operator ()stdexec::__let::__let_t485     auto operator()(_Fun __fun) const -> __binder_back<__let_t, _Fun>
486     {
487         return {{static_cast<_Fun&&>(__fun)}, {}, {}};
488     }
489 
490     using _Sender = __1;
491     using _Function = __0;
492     using __legacy_customizations_t =
493         __types<tag_invoke_t(__let_t,
494                              get_completion_scheduler_t<set_value_t>(
495                                  get_env_t(const _Sender&)),
496                              _Sender, _Function),
497                 tag_invoke_t(__let_t, _Sender, _Function)>;
498 
499     template <sender_expr_for<__let_t<_Set>> _Sender, class _Env>
transform_envstdexec::__let::__let_t500     static auto transform_env(_Sender&& __sndr, const _Env& __env)
501         -> decltype(auto)
502     {
503         return __sexpr_apply(static_cast<_Sender&&>(__sndr),
504                              __mk_transform_env_fn<__let_t<_Set>>(__env));
505     }
506 
507     template <sender_expr_for<__let_t<_Set>> _Sender, class _Env>
508         requires same_as<__early_domain_of_t<_Sender>, dependent_domain>
transform_senderstdexec::__let::__let_t509     static auto transform_sender(_Sender&& __sndr, const _Env& __env)
510         -> decltype(auto)
511     {
512         return __sexpr_apply(static_cast<_Sender&&>(__sndr),
513                              __mk_transform_sender_fn<__let_t<_Set>>(__env));
514     }
515 };
516 
517 template <class _Set, class _Domain>
518 struct __let_impl : __sexpr_defaults
519 {
520     static constexpr auto get_attrs = //
521         []<class _Child>(__ignore, const _Child& __child) noexcept {
522             return __env::__join(prop{get_domain, _Domain()},
523                                  stdexec::get_env(__child));
524         };
525 
526     static constexpr auto get_completion_signatures = //
527         []<class _Self, class... _Env>(_Self&&, _Env&&...) noexcept
528         -> __completions<__let_t<_Set, _Domain>, __data_of<_Self>,
529                          __child_of<_Self>, _Env...> {
530         static_assert(sender_expr_for<_Self, __let_t<_Set, _Domain>>);
531         return {};
532     };
533 
534     static constexpr auto get_state = //
535         []<class _Sender, class _Receiver>(_Sender&& __sndr, _Receiver&) {
536             static_assert(sender_expr_for<_Sender, __let_t<_Set, _Domain>>);
537             using _Fun = __data_of<_Sender>;
538             using _Child = __child_of<_Sender>;
539             using _Sched = __completion_sched<_Child, _Set>;
540             using __mk_let_state =
541                 __mbind_front_q<__let_state, _Receiver, _Fun, _Set, _Sched>;
542 
543             using __let_state_t =
544                 __gather_completions_of<_Set, _Child, env_of_t<_Receiver>,
545                                         __q<__decayed_tuple>, __mk_let_state>;
546 
547             return __sndr.apply(
548                 static_cast<_Sender&&>(__sndr),
549                 [&]<class _Fn, class _Child>(__ignore, _Fn&& __fn,
550                                              _Child&& __child) {
551                     _Sched __sched = query_or(get_completion_scheduler<_Set>,
552                                               stdexec::get_env(__child),
553                                               __unknown_scheduler());
554                     return __let_state_t{static_cast<_Fn&&>(__fn), __sched};
555                 });
556         };
557 
558     //! Helper function to actually invoke the function to produce `let_*`'s
559     //! sender, connect it to the downstream receiver, and start it. This is the
560     //! heart of `let_*`.
561     template <class _State, class _OpState, class... _As>
__bind_stdexec::__let::__let_impl562     static void __bind_(_State& __state, _OpState& __op_state, _As&&... __as)
563     {
564         // Store the passed-in (received) args:
565         auto& __args = __state.__args_.emplace_from(
566             __tup::__mktuple, static_cast<_As&&>(__as)...);
567         // Apply the function to the args to get the sender:
568         auto __sndr2 = __args.apply(std::move(__state.__fun_), __args);
569         // Create a receiver based on the state, the computed sender, and the
570         // operation state:
571         auto __rcvr2 = __state.__get_result_receiver(__sndr2, __op_state);
572         // Connect the sender to the receiver and start it:
573         auto& __op2 = __state.__op_state3_.emplace_from(
574             stdexec::connect, std::move(__sndr2), std::move(__rcvr2));
575         stdexec::start(__op2);
576     }
577 
578     template <class _OpState, class... _As>
__bindstdexec::__let::__let_impl579     static void __bind(_OpState& __op_state, _As&&... __as) noexcept
580     {
581         using _State = decltype(__op_state.__state_);
582         using _Receiver = decltype(__op_state.__rcvr_);
583         using _Fun = typename _State::__fun_t;
584         using _Sched = typename _State::__sched_t;
585         using _ResultSender =
586             __mcall<__result_sender_fn<_Set, _Fun, _Sched, env_of_t<_Receiver>>,
587                     _As...>;
588 
589         _State& __state = __op_state.__state_;
590         _Receiver& __rcvr = __op_state.__rcvr_;
591 
592         if constexpr ((__nothrow_decay_copyable<_As> && ...) &&
593                       __nothrow_callable<_Fun, _As...> &&
594                       __v<__nothrow_connectable_receiver_ref_t<
595                           _ResultSender, _Sched, env_of_t<_Receiver>>>)
596         {
597             __bind_(__state, __op_state, static_cast<_As&&>(__as)...);
598         }
599         else
600         {
601             try
602             {
603                 __bind_(__state, __op_state, static_cast<_As&&>(__as)...);
604             }
605             catch (...)
606             {
607                 using _Receiver = decltype(__op_state.__rcvr_);
608                 stdexec::set_error(static_cast<_Receiver&&>(__rcvr),
609                                    std::current_exception());
610             }
611         }
612     }
613 
614     static constexpr auto complete = //
615         []<class _OpState, class _Tag, class... _As>(
616             __ignore, _OpState& __op_state, _Tag,
617             _As&&... __as) noexcept -> void {
618         if constexpr (__same_as<_Tag, _Set>)
619         {
620             // Intercept the channel of interest to compute the sender and
621             // connect it:
622             __bind(__op_state, static_cast<_As&&>(__as)...);
623         }
624         else
625         {
626             // Forward the other channels downstream:
627             using _Receiver = decltype(__op_state.__rcvr_);
628             _Tag()(static_cast<_Receiver&&>(__op_state.__rcvr_),
629                    static_cast<_As&&>(__as)...);
630         }
631     };
632 };
633 } // namespace __let
634 
635 using let_value_t = __let::__let_t<set_value_t>;
636 inline constexpr let_value_t let_value{};
637 
638 using let_error_t = __let::__let_t<set_error_t>;
639 inline constexpr let_error_t let_error{};
640 
641 using let_stopped_t = __let::__let_t<set_stopped_t>;
642 inline constexpr let_stopped_t let_stopped{};
643 
644 template <class _Set, class _Domain>
645 struct __sexpr_impl<__let::__let_t<_Set, _Domain>> :
646     __let::__let_impl<_Set, _Domain>
647 {};
648 } // namespace stdexec
649