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