From 4c40dc41513ee8be7dce441f0d70ca355ef14d9f Mon Sep 17 00:00:00 2001 From: romintomasetti Date: Tue, 6 Oct 2026 06:49:40 +0000 Subject: [PATCH 1/2] stdexec(then): noexcept-ness Signed-off-by: romintomasetti --- include/stdexec/__detail/__then.hpp | 1 + test/stdexec/algos/adaptors/test_then.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/include/stdexec/__detail/__then.hpp b/include/stdexec/__detail/__then.hpp index 1a86eece8..bbf6a3ddc 100644 --- a/include/stdexec/__detail/__then.hpp +++ b/include/stdexec/__detail/__then.hpp @@ -240,6 +240,7 @@ namespace STDEXEC template <__movable_value _Fun> STDEXEC_ATTRIBUTE(always_inline, host, device) constexpr auto operator()(_Fun __fun) const + noexcept(__nothrow_decay_copyable<_Fun>) { return __closure(*this, static_cast<_Fun&&>(__fun)); } diff --git a/test/stdexec/algos/adaptors/test_then.cpp b/test/stdexec/algos/adaptors/test_then.cpp index 88e85e0c3..1292d522d 100644 --- a/test/stdexec/algos/adaptors/test_then.cpp +++ b/test/stdexec/algos/adaptors/test_then.cpp @@ -179,6 +179,17 @@ namespace | ex::then([] { return std::string{"hello"}; })); } + TEST_CASE("then noexceptness", "[adaptors][then]") + { + auto func = [](){}; + + STATIC_REQUIRE(noexcept(ex::then(func))); + + STATIC_REQUIRE(noexcept(STDEXEC::just() | STDEXEC::then(func))); + + STATIC_REQUIRE(noexcept(STDEXEC::then(STDEXEC::just(), func))); + } + TEST_CASE("then keeps error_types from input sender", "[adaptors][then]") { inline_scheduler sched1{}; From 7d3ea15a3b120e381946cdb34287d73c721891f0 Mon Sep 17 00:00:00 2001 From: romintomasetti Date: Mon, 5 Oct 2026 17:45:28 +0000 Subject: [PATCH 2/2] exec(fork_join): coalesce empty and unary calls Similar to https://github.com/NVIDIA/stdexec/pull/2124. See P4269R1. Signed-off-by: romintomasetti --- include/exec/fork_join.hpp | 24 ++++++++++++++++++++++-- test/exec/test_fork_join.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/include/exec/fork_join.hpp b/include/exec/fork_join.hpp index 921c84d86..c306dbd66 100644 --- a/include/exec/fork_join.hpp +++ b/include/exec/fork_join.hpp @@ -281,9 +281,28 @@ namespace experimental::execution struct fork_join_t { - template + /// No closure given. + template STDEXEC_ATTRIBUTE(host, device) - constexpr auto operator()(Sndr&& sndr, Closures&&... closures) const // + constexpr auto operator()(Sndr&& sndr) const noexcept(STDEXEC::__nothrow_decay_copyable) + { + return static_cast(sndr); + } + + /// Unary closure. + template requires (!STDEXEC::sender) + STDEXEC_ATTRIBUTE(host, device) + constexpr auto operator()(Sndr&& sndr, Closure&& clsr) const + noexcept(STDEXEC::__nothrow_callable) + { + return static_cast(clsr)(static_cast(sndr)); + } + + /// One sender and multiple closures. + template requires (sizeof...(Closures) > 1) + STDEXEC_ATTRIBUTE(host, device) + constexpr auto operator()(Sndr&& sndr, Closures&&... closures) const + noexcept(STDEXEC::__nothrow_decay_copyable) -> STDEXEC::__well_formed_sender auto { return STDEXEC::__sexpr{fork_join_t(), @@ -291,6 +310,7 @@ namespace experimental::execution static_cast(sndr)}; } + /// One or more closures. template requires((!STDEXEC::sender) && ...) STDEXEC_ATTRIBUTE(host, device) diff --git a/test/exec/test_fork_join.cpp b/test/exec/test_fork_join.cpp index a2d6d41b7..05006230d 100644 --- a/test/exec/test_fork_join.cpp +++ b/test/exec/test_fork_join.cpp @@ -43,6 +43,30 @@ namespace completion_signatures>); } + TEST_CASE("fork_join coalesces empty and unary calls", "[adaptors][fork_join]") + { + /// Empty (no closure given). + STDEXEC::sender auto empty = exec::fork_join(STDEXEC::just()); + using empty_t = decltype(empty); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(!exec::sender_for); + STATIC_REQUIRE(noexcept(exec::fork_join(STDEXEC::just()))); + + auto then = STDEXEC::then([]() noexcept {}); + + /// Unary closure. + STDEXEC::sender auto unary = exec::fork_join(STDEXEC::just(), then); + using unary_t = decltype(unary); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(!exec::sender_for); + STATIC_REQUIRE(noexcept(exec::fork_join(STDEXEC::just(), then))); + + /// Multiple closures. + STDEXEC::sender auto multiple = STDEXEC::just() | exec::fork_join(then, then); + STATIC_REQUIRE(exec::sender_for); + STATIC_REQUIRE(noexcept(exec::fork_join(STDEXEC::just(), then, then))); + } + struct ForwardingThen { template