diff --git a/libs/core/async_mpi/include/hpx/async_mpi/transform_mpi.hpp b/libs/core/async_mpi/include/hpx/async_mpi/transform_mpi.hpp index c604dba0501d..4559850fa782 100644 --- a/libs/core/async_mpi/include/hpx/async_mpi/transform_mpi.hpp +++ b/libs/core/async_mpi/include/hpx/async_mpi/transform_mpi.hpp @@ -73,7 +73,7 @@ namespace hpx::mpi::experimental { HPX_CXX_CORE_EXPORT template struct transform_mpi_receiver { - using is_receiver = void; + using receiver_concept = hpx::execution::experimental::receiver_t; HPX_NO_UNIQUE_ADDRESS std::decay_t r; HPX_NO_UNIQUE_ADDRESS std::decay_t f; @@ -85,27 +85,21 @@ namespace hpx::mpi::experimental { } template - friend constexpr void tag_invoke( - hpx::execution::experimental::set_error_t, - transform_mpi_receiver&& r, E&& e) noexcept + void set_error(E&& e) && noexcept { hpx::execution::experimental::set_error( - HPX_MOVE(r.r), HPX_FORWARD(E, e)); + HPX_MOVE(r), HPX_FORWARD(E, e)); } - friend constexpr void tag_invoke( - hpx::execution::experimental::set_stopped_t, - transform_mpi_receiver&& r) noexcept + void set_stopped() && noexcept { - hpx::execution::experimental::set_stopped(HPX_MOVE(r.r)); - }; + hpx::execution::experimental::set_stopped(HPX_MOVE(r)); + } template >> - friend constexpr void tag_invoke( - hpx::execution::experimental::set_value_t, - transform_mpi_receiver&& r, Ts&&... ts) noexcept + void set_value(Ts&&... ts) && noexcept { hpx::detail::try_catch_exception_ptr( [&]() { @@ -113,27 +107,30 @@ namespace hpx::mpi::experimental { Ts..., MPI_Request*>>) { MPI_Request request; - HPX_INVOKE(r.f, ts..., &request); + HPX_INVOKE(f, ts..., &request); // When the return type is void, there is no value // to forward to the receiver set_value_request_callback_void( - request, HPX_MOVE(r.r), HPX_FORWARD(Ts, ts)...); + request, HPX_MOVE(r), HPX_FORWARD(Ts, ts)...); } else { MPI_Request request; // When the return type is non-void, we have to - // forward the value to the receiver - auto&& result = HPX_INVOKE( - r.f, HPX_FORWARD(Ts, ts)..., &request); + // forward the value to the receiver. Pass `ts...` + // unforwarded into `f` so the same arguments can + // be moved once into the keep-alive callback + // below; this matches the void branch above and + // avoids a double-move when `Ts...` are rvalues. + auto&& result = HPX_INVOKE(f, ts..., &request); set_value_request_callback_non_void(request, - HPX_MOVE(r.r), HPX_MOVE(result), + HPX_MOVE(r), HPX_MOVE(result), HPX_FORWARD(Ts, ts)...); } }, [&](std::exception_ptr ep) { hpx::execution::experimental::set_error( - HPX_MOVE(r.r), HPX_MOVE(ep)); + HPX_MOVE(r), HPX_MOVE(ep)); }); } }; @@ -204,22 +201,18 @@ namespace hpx::mpi::experimental { // clang-format on template - friend constexpr auto tag_invoke( - hpx::execution::experimental::connect_t, - transform_mpi_sender& s, R&& r) + constexpr auto connect(R&& r) & { return hpx::execution::experimental::connect( - s.s, transform_mpi_receiver(HPX_FORWARD(R, r), s.f)); + s, transform_mpi_receiver(HPX_FORWARD(R, r), f)); } template - friend constexpr auto tag_invoke( - hpx::execution::experimental::connect_t, - transform_mpi_sender&& s, R&& r) + constexpr auto connect(R&& r) && { - return hpx::execution::experimental::connect(HPX_MOVE(s.s), + return hpx::execution::experimental::connect(HPX_MOVE(s), transform_mpi_receiver( - HPX_FORWARD(R, r), HPX_MOVE(s.f))); + HPX_FORWARD(R, r), HPX_MOVE(f))); } }; } // namespace detail diff --git a/libs/core/execution_base/include/hpx/execution_base/completion_signatures.hpp b/libs/core/execution_base/include/hpx/execution_base/completion_signatures.hpp index 947cad106e54..e0c6493ede24 100644 --- a/libs/core/execution_base/include/hpx/execution_base/completion_signatures.hpp +++ b/libs/core/execution_base/include/hpx/execution_base/completion_signatures.hpp @@ -106,21 +106,6 @@ namespace hpx::execution::experimental { } // namespace detail - namespace detail { - - HPX_CXX_CORE_EXPORT template - struct has_completion_signatures : std::false_type - { - }; - - HPX_CXX_CORE_EXPORT template - struct has_completion_signatures::completion_signatures>> - : std::true_type - { - }; - } // namespace detail // A sender is a type that is describing an asynchronous operation. The // operation itself might not have started yet. In order to get the result // of this asynchronous operation, a sender needs to be connected to a diff --git a/libs/core/execution_base/tests/unit/CMakeLists.txt b/libs/core/execution_base/tests/unit/CMakeLists.txt index f35ec7f81811..f308956bea2b 100644 --- a/libs/core/execution_base/tests/unit/CMakeLists.txt +++ b/libs/core/execution_base/tests/unit/CMakeLists.txt @@ -16,7 +16,6 @@ set(tests get_env execute_may_block_caller stdexec - test_tag_invoke_only_completion_signatures ) if(HPX_WITH_CXX20_COROUTINES) diff --git a/libs/core/execution_base/tests/unit/completion_signatures.cpp b/libs/core/execution_base/tests/unit/completion_signatures.cpp index 7a332a4a4ee0..1ab6f39c6272 100644 --- a/libs/core/execution_base/tests/unit/completion_signatures.cpp +++ b/libs/core/execution_base/tests/unit/completion_signatures.cpp @@ -382,8 +382,6 @@ void test_awaitable_sender1(Signatures&&, Awaiter&&) static_assert(ex::is_awaitable_v>); awaitable_sender_1 s; - static_assert(!hpx::meta::value< - ex::detail::has_completion_signatures>>); static_assert(std::is_same_v); diff --git a/libs/core/execution_base/tests/unit/coroutine_utils.cpp b/libs/core/execution_base/tests/unit/coroutine_utils.cpp index de5872bd4d89..56839ffc4b76 100644 --- a/libs/core/execution_base/tests/unit/coroutine_utils.cpp +++ b/libs/core/execution_base/tests/unit/coroutine_utils.cpp @@ -177,46 +177,6 @@ int main() { namespace ex = hpx::execution::experimental; - // clang-format off -#if !defined(HPX_HAVE_STDEXEC) - { - // clang-format off - static_assert( - std::is_same_v>, - int>); - static_assert( - std::is_same_v>, - void>); - // clang-format on - } -#endif - // clang-format on - - // single sender value -#if !defined(HPX_HAVE_STDEXEC) - { - static_assert(std::is_same_v< - ex::single_sender_value_t>, bool>); - static_assert(std::is_same_v< - ex::single_sender_value_t>, - void>); - } -#endif - - // connect awaitable -#if !defined(HPX_HAVE_STDEXEC) - { - // Verify that connect_awaitable and connect return the same operation state type - static_assert(std::is_same_v{}, - recv_set_value{})), - decltype(ex::connect( - awaitable_sender_1{}, recv_set_value{}))>); - } -#endif - // Promise env { static_assert(ex::is_awaiter_v); @@ -255,25 +215,13 @@ int main() awaiter>); } - // Operation base -#if !defined(HPX_HAVE_STDEXEC) - { - using operation_type = decltype(ex::connect( - awaitable_sender_1{}, recv_set_value{})); - static_assert(ex::is_operation_state_v); - } -#endif - - // Connect result type -#if !defined(HPX_HAVE_STDEXEC) - { - using operation_type = decltype(ex::connect( - awaitable_sender_1{}, recv_set_value{})); - static_assert(std::is_same_v< - ex::connect_result_t, recv_set_value>, - operation_type>); - } -#endif + // Note: tests for `single_sender_value_t>`, + // `single_sender_value_t>`, `connect_awaitable` + // and `connect_result_t` were removed in the + // post-stdexec cleanup. Under stdexec, awaitables are not standalone + // senders outside a coroutine context (they require + // `with_awaitable_senders`), so those tests relied on HPX's removed + // awaitable-as-sender path and are no longer applicable. // As awaitable { diff --git a/libs/core/execution_base/tests/unit/test_tag_invoke_only_completion_signatures.cpp b/libs/core/execution_base/tests/unit/test_tag_invoke_only_completion_signatures.cpp deleted file mode 100644 index bf1850d9db59..000000000000 --- a/libs/core/execution_base/tests/unit/test_tag_invoke_only_completion_signatures.cpp +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) 2026 Pratyksh Gupta -// -// SPDX-License-Identifier: BSL-1.0 -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - -// Exhaustive tests to verify that completion_signatures are only obtained via -// tag_invoke(get_completion_signatures_t, ...) and that the fallback to nested -// type aliases has been removed (P3164). -// -// This test file contains compile-time tests that verify the removal of -// has_completion_signatures and the nested type alias fallback mechanism. - -#include - -#if !defined(HPX_HAVE_STDEXEC) - -#include -#include -#include - -#include -#include - -namespace ex = hpx::execution::experimental; - -/////////////////////////////////////////////////////////////////////////////// -// Test 1: Sender with tag_invoke ONLY (should work) -/////////////////////////////////////////////////////////////////////////////// - -struct sender_with_tag_invoke_only -{ - using is_sender = void; - - // No nested completion_signatures type alias - - template - friend auto tag_invoke(ex::get_completion_signatures_t, - sender_with_tag_invoke_only const&, Env&&) noexcept - -> ex::completion_signatures; -}; - -void test_sender_with_tag_invoke_only() -{ - static_assert(ex::is_sender_v, - "Sender with tag_invoke should be recognized"); - - using sigs = ex::completion_signatures_of_t; - static_assert(std::is_same_v>, - "Should get completion signatures via tag_invoke"); -} - -/////////////////////////////////////////////////////////////////////////////// -// Test 2: Sender with BOTH tag_invoke and nested alias (tag_invoke wins) -/////////////////////////////////////////////////////////////////////////////// - -struct sender_with_both -{ - using is_sender = void; - - // Has nested alias (should be ignored since fallback is removed) - using completion_signatures_IGNORED = - ex::completion_signatures; // different from tag_invoke - - // tag_invoke should be the ONLY mechanism - template - friend auto tag_invoke(ex::get_completion_signatures_t, - sender_with_both const&, Env&&) noexcept - -> ex::completion_signatures; -}; - -void test_sender_with_both() -{ - using sigs = ex::completion_signatures_of_t; - static_assert(std::is_same_v>, - "tag_invoke should be used"); - - // Verify it's NOT using a nested alias - static_assert(!std::is_same_v>, - "Should not use nested completion_signatures alias"); -} - -/////////////////////////////////////////////////////////////////////////////// -// Test 3: Template sender with different signatures -/////////////////////////////////////////////////////////////////////////////// - -template -struct template_sender -{ - using is_sender = void; - - template - friend auto tag_invoke( - ex::get_completion_signatures_t, template_sender const&, Env&&) noexcept - -> ex::completion_signatures; -}; - -void test_template_sender() -{ - using sigs_int = ex::completion_signatures_of_t>; - static_assert(std::is_same_v>, - "Template sender with int should work"); - - using sigs_double = ex::completion_signatures_of_t>; - static_assert(std::is_same_v>, - "Template sender with double should work"); -} - -/////////////////////////////////////////////////////////////////////////////// -// Test 4: Sender with multiple completion signatures -/////////////////////////////////////////////////////////////////////////////// - -struct multi_sig_sender -{ - using is_sender = void; - - template - friend auto tag_invoke(ex::get_completion_signatures_t, - multi_sig_sender const&, Env&&) noexcept - -> ex::completion_signatures; -}; - -void test_multiple_signatures() -{ - using sigs = ex::completion_signatures_of_t; - static_assert( - std::is_same_v>, - "Should support multiple completion signatures"); -} - -/////////////////////////////////////////////////////////////////////////////// -// Test 5: Sender with only set_stopped -/////////////////////////////////////////////////////////////////////////////// - -struct stopped_only_sender -{ - using is_sender = void; - - template - friend auto tag_invoke(ex::get_completion_signatures_t, - stopped_only_sender const&, Env&&) noexcept - -> ex::completion_signatures; -}; - -void test_stopped_only() -{ - using sigs = ex::completion_signatures_of_t; - static_assert( - std::is_same_v>, - "Should support stopped-only sender"); -} - -/////////////////////////////////////////////////////////////////////////////// -// Test 6: Different template parameter types -/////////////////////////////////////////////////////////////////////////////// - -template -struct conditional_sender -{ - using is_sender = void; - - template - friend auto tag_invoke(ex::get_completion_signatures_t, - conditional_sender const&, Env&&) noexcept -> std::conditional_t, - ex::completion_signatures>; -}; - -void test_conditional_sender() -{ - using sigs_true = ex::completion_signatures_of_t>; - static_assert(std::is_same_v>, - "Conditional sender with true should have stopped signal"); - - using sigs_false = - ex::completion_signatures_of_t>; - static_assert(std::is_same_v>, - "Conditional sender with false should not have stopped signal"); -} - -/////////////////////////////////////////////////////////////////////////////// -// Test 7: Verify tag_invoke takes precedence over any naming conventions -/////////////////////////////////////////////////////////////////////////////// - -struct tricky_sender -{ - using is_sender = void; - - // This should be IGNORED (it's not the exact name that was checked before) - struct completion_signatures - { - template