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