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