From f4c901784b22e1dd22d77a68a5d3d1be13107408 Mon Sep 17 00:00:00 2001 From: Robert Leahy Date: Wed, 20 May 2026 09:02:07 -0400 Subject: [PATCH] continues_on: Conditionally-noexcept connect Previously connecting a continues_on sender was never noexcept. Updated so it's conditionally noexcept (i.e. it's noexcept when obtaining and connecting the schedule sender are both noexcept). --- include/stdexec/__detail/__continues_on.hpp | 13 +++-- .../algos/adaptors/test_continues_on.cpp | 48 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/include/stdexec/__detail/__continues_on.hpp b/include/stdexec/__detail/__continues_on.hpp index 690004f36..d9fb2866b 100644 --- a/include/stdexec/__detail/__continues_on.hpp +++ b/include/stdexec/__detail/__continues_on.hpp @@ -109,15 +109,18 @@ namespace STDEXEC template struct __state : __state_base<_Sexpr, _Receiver> { - using __receiver2_t = __receiver2<_Sexpr, _Receiver>; + using __receiver2_t = __receiver2<_Sexpr, _Receiver>; + using __schedule_sender_t = schedule_result_t<_Scheduler&>; constexpr explicit __state(_Scheduler __sched, _Receiver&& __rcvr) + noexcept(__nothrow_callable + && __nothrow_connectable<__schedule_sender_t, __receiver2_t>) : __state::__state_base{static_cast<_Receiver&&>(__rcvr)} , __state2_(connect(schedule(__sched), __receiver2_t{this})) {} STDEXEC_IMMOVABLE(__state); - connect_result_t, __receiver2_t> __state2_; + connect_result_t<__schedule_sender_t, __receiver2_t> __state2_; }; //! @brief The @c continues_on sender's attributes. @@ -342,8 +345,10 @@ namespace STDEXEC } static constexpr auto __get_state = - [](_Sender&& __sndr, - _Receiver&& __rcvr) -> __state_for_t<_Sender, _Receiver> + [](_Sender&& __sndr, _Receiver&& __rcvr) noexcept( + __nothrow_constructible_from<__state_for_t<_Sender, _Receiver>, + __data_of<_Sender>&, + _Receiver>) -> __state_for_t<_Sender, _Receiver> requires sender_in<__child_of<_Sender>, __fwd_env_t>> { static_assert(__sender_for<_Sender, continues_on_t>); diff --git a/test/stdexec/algos/adaptors/test_continues_on.cpp b/test/stdexec/algos/adaptors/test_continues_on.cpp index 88428655c..2d2418ac4 100644 --- a/test/stdexec/algos/adaptors/test_continues_on.cpp +++ b/test/stdexec/algos/adaptors/test_continues_on.cpp @@ -31,6 +31,40 @@ using namespace std::chrono_literals; namespace { + struct potentially_throwing_connect_scheduler + { + using scheduler_concept = ex::scheduler_tag; + + struct sender + { + using sender_concept = ex::sender_tag; + using completion_signatures = ex::completion_signatures; + + template + struct opstate + { + void start() & noexcept + { + ex::set_value(static_cast(receiver_)); + } + + Receiver receiver_; + }; + + template + auto connect(Receiver receiver) const noexcept(false) -> opstate + { + return {static_cast(receiver)}; + } + }; + + auto schedule() const noexcept -> sender + { + return {}; + } + + auto operator==(potentially_throwing_connect_scheduler const &) const noexcept -> bool = default; + }; TEST_CASE("continues_on returns a sender", "[adaptors][continues_on]") { @@ -46,6 +80,20 @@ namespace (void) snd; } + TEST_CASE("continues_on is nothrow connectable when the scheduler is", + "[adaptors][continues_on]") + { + using receiver_t = ex::__receiver_archetype>; + + auto nothrow = ex::continues_on(ex::just(), inline_scheduler{}); + STATIC_REQUIRE(ex::__nothrow_connectable); + + auto potentially_throwing = + ex::continues_on(ex::just(), potentially_throwing_connect_scheduler{}); + STATIC_REQUIRE_FALSE( + ex::__nothrow_connectable); + } + TEST_CASE("continues_on simple example", "[adaptors][continues_on]") { auto snd = ex::continues_on(ex::just(13), inline_scheduler{});