diff --git a/.clang-tidy b/.clang-tidy index 285024e7fd72..cc2931734810 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -15,6 +15,7 @@ Checks: > -bugprone-easily-swappable-parameters, -bugprone-reserved-identifier, -bugprone-lambda-function-name, + -bugprone-unchecked-optional-access, modernize-use-nullptr, misc-assert-side-effect misc-dangling-handle diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2dc47c15bb71..adb814005432 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -21,7 +21,7 @@ concurrency: jobs: configure-core-core_local-components: runs-on: ubuntu-latest - container: stellargroup/build_env:17 + container: stellargroup/build_env:19 outputs: test_matrix: ${{ steps.gen_matrix.outputs.matrix }} @@ -60,7 +60,7 @@ jobs: -DHPX_WITH_COMPILER_WARNINGS=On \ -DHPX_WITH_COMPILER_WARNINGS_AS_ERRORS=On \ -DHPX_WITH_DEPRECATION_WARNINGS=On \ - -DCMAKE_CXX_CLANG_TIDY=clang-tidy-20 \ + -DCMAKE_CXX_CLANG_TIDY=clang-tidy-21 \ -DHPX_WITH_THREAD_LOCAL_STORAGE=On \ -DHPX_WITH_STACKTRACES_STATIC_SYMBOLS=On \ -DHPX_WITH_STACKTRACES_DEMANGLE_SYMBOLS=Off \ @@ -116,7 +116,7 @@ jobs: tests-dynamic: needs: configure-core-core_local-components runs-on: ubuntu-latest - container: stellargroup/build_env:17 + container: stellargroup/build_env:19 name: ${{ matrix.name }} (${{ matrix.count }} tests) strategy: fail-fast: false @@ -134,4 +134,4 @@ jobs: build-artifact-name: hpx-build-${{ github.sha }} build-targets: ${{ matrix.targets }} ctest-regex: ${{ matrix.tests }} - artifact-job-name: ${{ matrix.name }} + artifact-job-name: ${{ matrix.name }} \ No newline at end of file diff --git a/.github/workflows/linux_debug_modules.yml b/.github/workflows/linux_release_modules.yml similarity index 79% rename from .github/workflows/linux_debug_modules.yml rename to .github/workflows/linux_release_modules.yml index 5639b1a45c28..1bda1a95cf3b 100644 --- a/.github/workflows/linux_debug_modules.yml +++ b/.github/workflows/linux_release_modules.yml @@ -1,11 +1,11 @@ # Copyright (c) 2020 ETH Zurich -# Copyright (c) 2024 The STE||AR Group +# Copyright (c) 2024-2026 The STE||AR Group # # 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) -name: Linux CI (Debug, C++20 Modules) +name: Linux CI (Release, C++20 Modules) on: [pull_request] @@ -16,7 +16,7 @@ concurrency: jobs: build: runs-on: ubuntu-latest - container: stellargroup/build_env:19 + container: stellargroup/build_env:22 steps: - uses: actions/checkout@v6 @@ -28,7 +28,7 @@ jobs: -Bbuild \ -GNinja \ -DHPX_WITH_CXX_STANDARD=23 \ - -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_BUILD_TYPE=Release \ -DHPX_WITH_MALLOC=system \ -DHPX_WITH_FETCH_ASIO=ON \ -DHPX_WITH_ASIO_TAG=asio-1-34-2 \ @@ -37,8 +37,10 @@ jobs: -DHPX_WITH_TESTS_MAX_THREADS_PER_LOCALITY=2 \ -DHPX_WITH_VERIFY_LOCKS=ON \ -DHPX_WITH_VERIFY_LOCKS_BACKTRACE=ON \ - -DHPX_WITH_CHECK_MODULE_DEPENDENCIES=On \ - -DHPX_WITH_CXX_MODULES=ON + -DHPX_WITH_CHECK_MODULE_DEPENDENCIES=ON \ + -DHPX_WITH_CXX_MODULES=ON \ + -DHPX_WITH_DATAPAR=ON \ + -DHPX_WITH_DATAPAR_BACKEND=EMULATE - name: Build shell: bash run: | diff --git a/.github/workflows/macos_debug.yml b/.github/workflows/macos_debug.yml index ff66ce693391..f338d3f4fb48 100644 --- a/.github/workflows/macos_debug.yml +++ b/.github/workflows/macos_debug.yml @@ -1,5 +1,5 @@ # Copyright (c) 2020 Mikael Simberg -# Copyright (c) 2024 The STE||AR Group +# Copyright (c) 2024-2026 The STE||AR Group # # SPDX-License-Identifier: BSL-1.0 # Distributed under the Boost Software License, Version 1.0. (See accompanying @@ -44,7 +44,7 @@ jobs: shell: bash run: | cmake --build build --target all - cmake --build build --target tests + cmake --build build --target tests -- -k 0 - name: Test shell: bash run: | @@ -83,4 +83,5 @@ jobs: tests.unit.modules.executors.limiting_executor|\ tests.unit.modules.compute_local.block_fork_join_executor|\ tests.unit.modules.algorithms.algorithms.all_of|\ - tests.unit.modules.algorithms.algorithms.any_of" + tests.unit.modules.algorithms.algorithms.any_of|\ + tests.unit.modules.algorithms.algorithms.uninitialized_relocate_sender" diff --git a/.github/workflows/macos_debug_fetch_boost.yml b/.github/workflows/macos_debug_fetch_boost.yml index fa101aa2a65c..6abd9f1c74ed 100644 --- a/.github/workflows/macos_debug_fetch_boost.yml +++ b/.github/workflows/macos_debug_fetch_boost.yml @@ -42,7 +42,7 @@ jobs: shell: bash run: | cmake --build build --target all - cmake --build build --target tests + cmake --build build --target tests -- -k 0 - name: Test shell: bash run: | diff --git a/.github/workflows/macos_debug_fetch_hwloc.yml b/.github/workflows/macos_debug_fetch_hwloc.yml index c664638da39a..06f82c38cf3d 100644 --- a/.github/workflows/macos_debug_fetch_hwloc.yml +++ b/.github/workflows/macos_debug_fetch_hwloc.yml @@ -1,5 +1,5 @@ # Copyright (c) 2024 Vedant Nimje -# Copyright (c) 2024 The STE||AR Group +# Copyright (c) 2024-2026 The STE||AR Group # # SPDX-License-Identifier: BSL-1.0 # Distributed under the Boost Software License, Version 1.0. (See accompanying @@ -48,7 +48,7 @@ jobs: shell: bash run: | cmake --build build --target all - cmake --build build --target tests + cmake --build build --target tests -- -k 0 - name: Test shell: bash run: | diff --git a/libs/core/algorithms/include/hpx/parallel/algorithms/find.hpp b/libs/core/algorithms/include/hpx/parallel/algorithms/find.hpp index ed5baf3034ef..eb03cc279712 100644 --- a/libs/core/algorithms/include/hpx/parallel/algorithms/find.hpp +++ b/libs/core/algorithms/include/hpx/parallel/algorithms/find.hpp @@ -1356,7 +1356,7 @@ namespace hpx::parallel { HPX_FORWARD(Proj1, proj1), HPX_FORWARD(Proj2, proj2)); }; - auto f2 = [tok, first1]( + auto f2 = [tok, first1, last1]( auto&&... data) mutable -> Iter1 { static_assert(sizeof...(data) < 2); @@ -1366,6 +1366,11 @@ namespace hpx::parallel { difference_type find_end_res = tok.get_data(); + if (find_end_res < 0) + { + return advance_to_sentinel(first1, last1); + } + std::advance(first1, find_end_res); return first1; }; diff --git a/libs/core/algorithms/include/hpx/parallel/algorithms/remove.hpp b/libs/core/algorithms/include/hpx/parallel/algorithms/remove.hpp index 9296dcecb658..54e1d982d7eb 100644 --- a/libs/core/algorithms/include/hpx/parallel/algorithms/remove.hpp +++ b/libs/core/algorithms/include/hpx/parallel/algorithms/remove.hpp @@ -449,7 +449,9 @@ namespace hpx { using Type = typename std::iterator_traits::value_type; return hpx::remove_if(HPX_FORWARD(ExPolicy, policy), first, last, - [value](Type const& a) -> bool { return value == a; }); + [value](Type const& a) -> bool { + return static_cast(value) == a; + }); } } remove{}; } // namespace hpx diff --git a/libs/core/algorithms/include/hpx/parallel/algorithms/uninitialized_relocate.hpp b/libs/core/algorithms/include/hpx/parallel/algorithms/uninitialized_relocate.hpp index f4351b42ae26..dba4e2fdb8e0 100644 --- a/libs/core/algorithms/include/hpx/parallel/algorithms/uninitialized_relocate.hpp +++ b/libs/core/algorithms/include/hpx/parallel/algorithms/uninitialized_relocate.hpp @@ -1102,8 +1102,20 @@ namespace hpx::experimental { // NOLINTNEXTLINE(bugprone-undefined-memory-manipulation) std::memmove(static_cast(std::to_address(dest)), std::to_address(first), count * sizeof(value_type)); - return parallel::util::detail::algorithm_result< - ExPolicy, FwdIter>::get(std::next(dest, count)); + + auto result = + parallel::util::detail::algorithm_result::get(std::next(dest, count)); + if constexpr (has_scheduler_executor) + { + namespace ex = hpx::execution::experimental; + return ex::unique_any_sender( + HPX_MOVE(result)); + } + else + { + return result; + } } } } @@ -1134,23 +1146,20 @@ namespace hpx::experimental { auto d = static_cast( std::to_address(first) - std::to_address(dest)); + auto result = parallel::util::get_second_element( + hpx::parallel::detail:: + parallel_uninitialized_relocate_n_overlap( + HPX_FORWARD(ExPolicy, policy), + first, count, dest, d)); if constexpr (has_scheduler_executor) { namespace ex = hpx::execution::experimental; - return ex::unique_any_sender< - FwdIter>(parallel::util::get_second_element( - hpx::parallel::detail:: - parallel_uninitialized_relocate_n_overlap( - HPX_FORWARD(ExPolicy, policy), - first, count, dest, d))); + return ex::unique_any_sender( + HPX_MOVE(result)); } else { - return parallel::util::get_second_element( - hpx::parallel::detail:: - parallel_uninitialized_relocate_n_overlap( - HPX_FORWARD(ExPolicy, policy), - first, count, dest, d)); + return result; } } } @@ -1159,46 +1168,37 @@ namespace hpx::experimental { if (!has_overlap) { + auto result = parallel::util::get_second_element( + hpx::parallel::detail::uninitialized_relocate_n< + parallel::util::in_out_result>() + .call(HPX_FORWARD(ExPolicy, policy), first, count, + dest)); if constexpr (has_scheduler_executor) { namespace ex = hpx::execution::experimental; - return ex::unique_any_sender( - parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_n< - parallel::util::in_out_result>() - .call(HPX_FORWARD(ExPolicy, policy), first, - count, dest))); + return ex::unique_any_sender(HPX_MOVE(result)); } else { - return parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_n< - parallel::util::in_out_result>() - .call(HPX_FORWARD(ExPolicy, policy), first, - count, dest)); + return result; } } } + auto result = parallel::util::get_second_element( + hpx::parallel::detail::uninitialized_relocate_n< + parallel::util::in_out_result>() + .call(hpx::execution::seq, first, + static_cast(count), dest)); if constexpr (has_scheduler_executor) { namespace ex = hpx::execution::experimental; return ex::unique_any_sender( - ex::just(parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_n< - parallel::util::in_out_result>() - .call(hpx::execution::seq, first, - static_cast(count), dest)))); + ex::just(HPX_MOVE(result))); } else { - return parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_n< - parallel::util::in_out_result>() - .call(hpx::execution::seq, first, - static_cast(count), dest)); + return result; } } } uninitialized_relocate_n{}; @@ -1300,15 +1300,27 @@ namespace hpx::experimental { FwdIter>::is_memcpyable; if constexpr (is_trivially_relocatable) { - auto last = std::next(first, count); - if (first < dest && dest < last) + auto src_last = std::next(first, count); + if (first < dest && dest < src_last) { using value_type = std::iter_value_t; // NOLINTNEXTLINE(bugprone-undefined-memory-manipulation) std::memmove(static_cast(std::to_address(dest)), std::to_address(first), count * sizeof(value_type)); - return parallel::util::detail::algorithm_result< - ExPolicy, FwdIter>::get(std::next(dest, count)); + + if constexpr (has_scheduler_executor) + { + namespace ex = hpx::execution::experimental; + return ex::unique_any_sender( + parallel::util::detail::algorithm_result< + ExPolicy, FwdIter>::get(std::next(dest, + count))); + } + else + { + return parallel::util::detail::algorithm_result< + ExPolicy, FwdIter>::get(std::next(dest, count)); + } } } } @@ -1320,9 +1332,9 @@ namespace hpx::experimental { hpx::traits::is_contiguous_iterator_v && hpx::traits::is_contiguous_iterator_v) { - auto last = std::next(first, count); + auto src_last = std::next(first, count); auto dest_last = std::next(dest, count); - has_overlap = (first < dest_last) && (dest_last < last); + has_overlap = (first < dest_last) && (dest_last < src_last); if (has_overlap) { @@ -1340,23 +1352,20 @@ namespace hpx::experimental { auto d = static_cast( std::to_address(first) - std::to_address(dest)); + auto result = parallel::util::get_second_element( + hpx::parallel::detail:: + parallel_uninitialized_relocate_n_overlap( + HPX_FORWARD(ExPolicy, policy), + first, count, dest, d)); if constexpr (has_scheduler_executor) { namespace ex = hpx::execution::experimental; - return ex::unique_any_sender< - FwdIter>(parallel::util::get_second_element( - hpx::parallel::detail:: - parallel_uninitialized_relocate_n_overlap( - HPX_FORWARD(ExPolicy, policy), - first, count, dest, d))); + return ex::unique_any_sender( + HPX_MOVE(result)); } else { - return parallel::util::get_second_element( - hpx::parallel::detail:: - parallel_uninitialized_relocate_n_overlap( - HPX_FORWARD(ExPolicy, policy), - first, count, dest, d)); + return result; } } } @@ -1365,44 +1374,36 @@ namespace hpx::experimental { if (!has_overlap) { + auto result = parallel::util::get_second_element( + hpx::parallel::detail::uninitialized_relocate_n< + parallel::util::in_out_result>() + .call(HPX_FORWARD(ExPolicy, policy), first, count, + dest)); if constexpr (has_scheduler_executor) { namespace ex = hpx::execution::experimental; - return ex::unique_any_sender( - parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_n< - parallel::util::in_out_result>() - .call(HPX_FORWARD(ExPolicy, policy), first, - count, dest))); + return ex::unique_any_sender(HPX_MOVE(result)); } else { - return parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_n< - parallel::util::in_out_result>() - .call(HPX_FORWARD(ExPolicy, policy), first, - count, dest)); + return result; } } } + auto result = parallel::util::get_second_element( + hpx::parallel::detail::uninitialized_relocate_n< + parallel::util::in_out_result>() + .call(hpx::execution::seq, first, count, dest)); if constexpr (has_scheduler_executor) { namespace ex = hpx::execution::experimental; return ex::unique_any_sender( - ex::just(parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_n< - parallel::util::in_out_result>() - .call(hpx::execution::seq, first, count, dest)))); + ex::just(HPX_MOVE(result))); } else { - return parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_n< - parallel::util::in_out_result>() - .call(hpx::execution::seq, first, count, dest)); + return result; } } } uninitialized_relocate{}; @@ -1515,23 +1516,22 @@ namespace hpx::experimental { auto d = static_cast( std::to_address(dest_first) - std::to_address(first)); + + auto result = parallel::util::get_second_element( + hpx::parallel::detail:: + parallel_uninitialized_relocate_n_bwd_overlap( + HPX_FORWARD(ExPolicy, policy), + first, count, dest_last, d)); + if constexpr (has_scheduler_executor) { namespace ex = hpx::execution::experimental; - return ex::unique_any_sender< - BiIter2>(parallel::util::get_second_element( - hpx::parallel::detail:: - parallel_uninitialized_relocate_n_bwd_overlap( - HPX_FORWARD(ExPolicy, policy), - first, count, dest_last, d))); + return ex::unique_any_sender( + HPX_MOVE(result)); } else { - return parallel::util::get_second_element( - hpx::parallel::detail:: - parallel_uninitialized_relocate_n_bwd_overlap( - HPX_FORWARD(ExPolicy, policy), - first, count, dest_last, d)); + return result; } } } @@ -1540,45 +1540,36 @@ namespace hpx::experimental { if (!has_overlap) { + auto result = parallel::util::get_second_element( + hpx::parallel::detail::uninitialized_relocate_backward< + parallel::util::in_out_result>() + .call(HPX_FORWARD(ExPolicy, policy), first, last, + dest_last)); if constexpr (has_scheduler_executor) { namespace ex = hpx::execution::experimental; - return ex::unique_any_sender( - parallel::util::get_second_element(hpx::parallel:: - detail::uninitialized_relocate_backward< - parallel::util::in_out_result>() - .call(HPX_FORWARD(ExPolicy, policy), - first, last, dest_last))); + return ex::unique_any_sender(HPX_MOVE(result)); } else { - return parallel::util::get_second_element( - hpx::parallel::detail:: - uninitialized_relocate_backward>() - .call(HPX_FORWARD(ExPolicy, policy), first, - last, dest_last)); + return result; } } } + auto result = parallel::util::get_second_element( + hpx::parallel::detail::uninitialized_relocate_backward< + parallel::util::in_out_result>() + .call(hpx::execution::seq, first, last, dest_last)); if constexpr (has_scheduler_executor) { namespace ex = hpx::execution::experimental; return ex::unique_any_sender( - ex::just(parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_backward< - parallel::util::in_out_result>() - .call( - hpx::execution::seq, first, last, dest_last)))); + ex::just(HPX_MOVE(result))); } else { - return parallel::util::get_second_element( - hpx::parallel::detail::uninitialized_relocate_backward< - parallel::util::in_out_result>() - .call(hpx::execution::seq, first, last, dest_last)); + return result; } } } uninitialized_relocate_backward{}; diff --git a/libs/core/algorithms/include/hpx/parallel/util/detail/partitioner_iteration.hpp b/libs/core/algorithms/include/hpx/parallel/util/detail/partitioner_iteration.hpp index 7bd10dfef025..b0244d2d6943 100644 --- a/libs/core/algorithms/include/hpx/parallel/util/detail/partitioner_iteration.hpp +++ b/libs/core/algorithms/include/hpx/parallel/util/detail/partitioner_iteration.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -34,14 +35,14 @@ namespace hpx::parallel::util::detail { hpx::tuple_size>::value>; // NOLINTBEGIN(bugprone-use-after-move) - if constexpr (std::is_invocable_v) + if constexpr (std::invocable) { return HPX_INVOKE_R( Result, f_, embedded_index_pack_type{}, HPX_FORWARD(T, t)); } else { - return (*this)(embedded_index_pack_type{}, t); + return (*this)(embedded_index_pack_type{}, HPX_FORWARD(T, t)); } // NOLINTEND(bugprone-use-after-move) } diff --git a/libs/core/algorithms/include/hpx/parallel/util/detail/sender_util.hpp b/libs/core/algorithms/include/hpx/parallel/util/detail/sender_util.hpp index d5f7703a12f3..eb0e33040dca 100644 --- a/libs/core/algorithms/include/hpx/parallel/util/detail/sender_util.hpp +++ b/libs/core/algorithms/include/hpx/parallel/util/detail/sender_util.hpp @@ -65,9 +65,22 @@ namespace hpx::detail { // returns senders, we don't need to wrap the algorithm in any // specific way as it directly integrates with the given // predecessor. - return hpx::execution::experimental::let_value( - HPX_FORWARD(Predecessor, predecessor), - bound_algorithm{HPX_FORWARD(ExPolicy, policy)}); + // + // We chain the result through `continues_on(sched)` so that the + // resulting sender's environment exposes + // `get_completion_scheduler` (and through it + // `get_completion_domain` -> `thread_pool_domain`). + // Without this, `sync_wait` on the returned sender resolves the + // completion domain as `default_domain`, which uses stdexec's + // run_loop and OS-blocks the calling thread (deadlocking when + // called from an HPX worker, especially with --hpx:threads=1). + auto sched = policy.executor().sched(); + return hpx::execution::experimental::continues_on( + hpx::execution::experimental::let_value( + HPX_FORWARD(Predecessor, predecessor), + bound_algorithm{ + HPX_FORWARD(ExPolicy, policy)}), + HPX_MOVE(sched)); } else if constexpr (hpx::execution::detail::has_async_execution_policy_v< ExPolicy>) diff --git a/libs/core/algorithms/include/hpx/parallel/util/loop.hpp b/libs/core/algorithms/include/hpx/parallel/util/loop.hpp index eeac8b84ff8a..26929c60dcd8 100644 --- a/libs/core/algorithms/include/hpx/parallel/util/loop.hpp +++ b/libs/core/algorithms/include/hpx/parallel/util/loop.hpp @@ -55,26 +55,6 @@ namespace hpx::parallel::util { template - requires( // forces hpx::execution::unseq - hpx::is_unsequenced_execution_policy_v && - !hpx::is_parallel_execution_policy_v) - HPX_HOST_DEVICE HPX_FORCEINLINE static Begin call( - ExPolicy&&, Begin it, End last, F&& f) - { - // clang-format off - HPX_IVDEP HPX_UNROLL HPX_VECTORIZE - for (Begin& iter = it; iter != last; ++iter) - { - HPX_INVOKE(f, iter); - } - // clang-format on - - return it; - } - - template - requires(!hpx::is_unsequenced_execution_policy_v) HPX_HOST_DEVICE HPX_FORCEINLINE static constexpr Begin call( ExPolicy&&, Begin it, End last, F&& f) { diff --git a/libs/core/algorithms/include/hpx/parallel/util/partitioner.hpp b/libs/core/algorithms/include/hpx/parallel/util/partitioner.hpp index 57f06c1108dd..396d2660817a 100644 --- a/libs/core/algorithms/include/hpx/parallel/util/partitioner.hpp +++ b/libs/core/algorithms/include/hpx/parallel/util/partitioner.hpp @@ -398,7 +398,29 @@ namespace hpx::parallel::util::detail { scoped_params.mark_end_of_scheduling(); - return reduce(HPX_MOVE(items), HPX_FORWARD(F2, f2)); + if constexpr (hpx::execution_policy_has_scheduler_executor_v< + ExPolicy_>) + { + namespace ex = hpx::execution::experimental; + namespace tt = hpx::this_thread::experimental; + auto sender = + ex::then(HPX_MOVE(items), HPX_FORWARD(F2, f2)); + auto result = tt::sync_wait(HPX_MOVE(sender)); + if constexpr (hpx::tuple_size_v< + std::decay_t> == 0) + { + return; + } + else + { + auto value = hpx::get<0>(HPX_MOVE(*result)); + return value; + } + } + else + { + return reduce(HPX_MOVE(items), HPX_FORWARD(F2, f2)); + } } catch (...) { diff --git a/libs/core/algorithms/include/hpx/parallel/util/partitioner_with_cleanup.hpp b/libs/core/algorithms/include/hpx/parallel/util/partitioner_with_cleanup.hpp index a63ef488ea47..d2c7d4e6d700 100644 --- a/libs/core/algorithms/include/hpx/parallel/util/partitioner_with_cleanup.hpp +++ b/libs/core/algorithms/include/hpx/parallel/util/partitioner_with_cleanup.hpp @@ -80,10 +80,10 @@ namespace hpx::parallel::util { if constexpr (has_scheduler_executor) { // Wrap f1 in a variant type to handle exceptions - auto wrapped_f1 = [f1 = HPX_FORWARD(F1, f1)]( + auto wrapped_f1 = [f1 = HPX_FORWARD(F1, f1)](FwdIter it, auto&&... args) mutable noexcept { using result_type = std::decay_t; + it, HPX_FORWARD(decltype(args), args)...))>; using nonvoid_result_type = std::conditional_t, std::monostate, result_type>; @@ -95,15 +95,17 @@ namespace hpx::parallel::util { { if constexpr (std::is_void_v) { - f1(HPX_FORWARD(decltype(args), args)...); + f1(it, + HPX_FORWARD(decltype(args), args)...); return variant_type{std::in_place_index<0>, std::monostate{}}; } else { return variant_type{std::in_place_index<0>, - f1(HPX_FORWARD( - decltype(args), args)...)}; + f1(it, + HPX_FORWARD( + decltype(args), args)...)}; } } catch (...) diff --git a/libs/core/algorithms/tests/performance/CMakeLists.txt b/libs/core/algorithms/tests/performance/CMakeLists.txt index e2fe7841e219..ff24e1bf0a91 100644 --- a/libs/core/algorithms/tests/performance/CMakeLists.txt +++ b/libs/core/algorithms/tests/performance/CMakeLists.txt @@ -30,16 +30,6 @@ set(benchmarks transform_reduce_scaling ) -# foreach_report uses tag_invoke-based sender patterns that do not compile with -# Clang < 22 / AppleClang < 18 against the current stdexec. -if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION - VERSION_LESS "22") - OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" - AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "18") -) - list(REMOVE_ITEM benchmarks foreach_report) -endif() - foreach(benchmark ${benchmarks}) set(sources ${benchmark}.cpp) diff --git a/libs/core/algorithms/tests/unit/algorithms/CMakeLists.txt b/libs/core/algorithms/tests/unit/algorithms/CMakeLists.txt index 34636c88a396..a6c3e3041839 100644 --- a/libs/core/algorithms/tests/unit/algorithms/CMakeLists.txt +++ b/libs/core/algorithms/tests/unit/algorithms/CMakeLists.txt @@ -161,87 +161,81 @@ if(HPX_WITH_CXX17_STD_EXECUTION_POLICES) set(tests ${tests} foreach_std_policies) endif() -if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - set(tests - ${tests} - adjacentdifference_sender - adjacentfind_sender - all_of_sender - any_of_sender - copy_sender - copyn_sender - count_sender - countif_sender - destroy_sender - destroyn_sender - ends_with_sender - equal_sender - equal_binary_sender - fill_sender - filln_sender - find_sender - findend_sender - findfirstof_sender - findif_sender - findifnot_sender - foreach_sender - foreachn_sender - generate_sender - generaten_sender - is_heap_sender - is_heap_until_sender - includes_sender - is_partitioned_sender - is_sorted_sender - is_sorted_until_sender - lexicographical_compare_sender - max_element_sender - min_element_sender - minmax_element_sender - mismatch_sender - mismatch_binary_sender - move_sender - none_of_sender - partial_sort_sender - reduce_sender - remove_sender - remove_if_sender - replace_sender - replace_if_sender - replace_copy_sender - replace_copy_if_sender - reverse_sender - reverse_copy_sender - rotate_sender - rotate_copy_sender - search_sender - starts_with_sender - swapranges_sender - transform_sender - transform_binary_sender - transform_binary2_sender - transform_reduce_sender - transform_reduce_binary_sender - uninitialized_copy_sender - uninitialized_copyn_sender - uninitialized_default_construct_sender - uninitialized_default_constructn_sender - uninitialized_fill_sender - uninitialized_filln_sender - uninitialized_move_sender - uninitialized_moven_sender - uninitialized_relocate_sender - uninitialized_relocate_backward_sender - uninitialized_relocaten_sender - uninitialized_value_construct_sender - uninitialized_value_constructn_sender - unique_sender - ) -endif() - -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - list(REMOVE_ITEM tests foreach_scheduler) -endif() +set(tests + ${tests} + adjacentdifference_sender + adjacentfind_sender + all_of_sender + any_of_sender + copy_sender + copyn_sender + count_sender + countif_sender + destroy_sender + destroyn_sender + ends_with_sender + equal_sender + equal_binary_sender + fill_sender + filln_sender + find_sender + findend_sender + findfirstof_sender + findif_sender + findifnot_sender + foreach_sender + foreachn_sender + generate_sender + generaten_sender + is_heap_sender + is_heap_until_sender + includes_sender + is_partitioned_sender + is_sorted_sender + is_sorted_until_sender + lexicographical_compare_sender + max_element_sender + min_element_sender + minmax_element_sender + mismatch_sender + mismatch_binary_sender + move_sender + none_of_sender + partial_sort_sender + reduce_sender + remove_sender + remove_if_sender + replace_sender + replace_if_sender + replace_copy_sender + replace_copy_if_sender + reverse_sender + reverse_copy_sender + rotate_sender + rotate_copy_sender + search_sender + starts_with_sender + swapranges_sender + transform_sender + transform_binary_sender + transform_binary2_sender + transform_reduce_sender + transform_reduce_binary_sender + uninitialized_copy_sender + uninitialized_copyn_sender + uninitialized_default_construct_sender + uninitialized_default_constructn_sender + uninitialized_fill_sender + uninitialized_filln_sender + uninitialized_move_sender + uninitialized_moven_sender + uninitialized_relocate_sender + uninitialized_relocate_backward_sender + uninitialized_relocaten_sender + uninitialized_value_construct_sender + uninitialized_value_constructn_sender + unique_sender +) foreach(test ${tests}) set(sources ${test}.cpp) @@ -269,3 +263,11 @@ foreach(test ${tests}) "modules.algorithms.algorithms" ${test} ${${test}_PARAMETERS} ) endforeach() + +if(HPX_WITH_CXX_MODULES AND (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) + foreach(test copyn_sender generate_sender min_element_sender) + target_compile_definitions( + ${test}_test PRIVATE HPX_HAVE_FORCE_NO_CXX_MODULES + ) + endforeach() +endif() diff --git a/libs/core/algorithms/tests/unit/algorithms/adjacentdifference_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/adjacentdifference_tests.hpp index b4505377b4a5..0ac37dc1ab98 100644 --- a/libs/core/algorithms/tests/unit/algorithms/adjacentdifference_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/adjacentdifference_tests.hpp @@ -92,7 +92,8 @@ void test_adjacent_difference_sender(Policy l, ExPolicy&& policy) auto snd_result = tt::sync_wait(ex::just(std::begin(c), std::end(c), std::begin(d)) | hpx::adjacent_difference(policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + HPX_TEST(snd_result.has_value()); + auto result = hpx::get<0>(snd_result.value()); std::adjacent_difference(std::begin(c), std::end(c), std::begin(d_ans)); @@ -106,7 +107,8 @@ void test_adjacent_difference_sender(Policy l, ExPolicy&& policy) auto snd_result = tt::sync_wait( ex::just(std::begin(c), std::begin(c), std::begin(d)) | hpx::adjacent_difference(policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + HPX_TEST(snd_result.has_value()); + auto result = hpx::get<0>(snd_result.value()); std::adjacent_difference( std::begin(c), std::begin(c), std::begin(d_ans)); @@ -121,7 +123,8 @@ void test_adjacent_difference_sender(Policy l, ExPolicy&& policy) auto snd_result = tt::sync_wait( ex::just(std::begin(c), ++std::begin(c), std::begin(d)) | hpx::adjacent_difference(policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + HPX_TEST(snd_result.has_value()); + auto result = hpx::get<0>(snd_result.value()); std::adjacent_difference( std::begin(c), ++std::begin(c), std::begin(d_ans)); @@ -178,6 +181,7 @@ void test_adjacent_difference_async_direct(Policy l, ExPolicy&& p) HPX_TEST(std::equal(std::begin(d), std::end(d), std::begin(d_ans), [](auto lhs, auto rhs) { return lhs == rhs; })); + HPX_TEST(result.has_value()); HPX_TEST(std::end(d) == hpx::get<0>(*result)); } diff --git a/libs/core/algorithms/tests/unit/algorithms/adjacentfind_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/adjacentfind_tests.hpp index 6011c9edccd4..0e7ff7392339 100644 --- a/libs/core/algorithms/tests/unit/algorithms/adjacentfind_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/adjacentfind_tests.hpp @@ -85,8 +85,9 @@ void test_adjacent_find_sender( auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::end(c))) | hpx::adjacent_find(ex_policy.on(exec))); + HPX_TEST(snd_result.has_value()); - iterator index = hpx::get<0>(*snd_result); + iterator index = hpx::get<0>(snd_result.value()); base_iterator test_index = std::begin(c) + static_cast(random_pos); @@ -99,7 +100,8 @@ void test_adjacent_find_sender( auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::begin(c))) | hpx::adjacent_find(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + HPX_TEST(snd_result.has_value()); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(iterator(std::begin(c)) == result); } diff --git a/libs/core/algorithms/tests/unit/algorithms/all_of_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/all_of_tests.hpp index 221af67c3ad4..46829f759290 100644 --- a/libs/core/algorithms/tests/unit/algorithms/all_of_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/all_of_tests.hpp @@ -93,8 +93,7 @@ void test_all_of_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::end(c)), [](auto v) { return v != 0; }) | hpx::all_of(ex_policy.on(exec))); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); // verify values bool expected = std::all_of( diff --git a/libs/core/algorithms/tests/unit/algorithms/any_of_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/any_of_tests.hpp index ba59fe50c33d..71865123ea3c 100644 --- a/libs/core/algorithms/tests/unit/algorithms/any_of_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/any_of_tests.hpp @@ -95,8 +95,7 @@ void test_any_of_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::end(c)), [](auto v) { return v != 0; }) | hpx::any_of(ex_policy.on(exec))); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); // verify values bool expected = std::any_of( diff --git a/libs/core/algorithms/tests/unit/algorithms/count_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/count_tests.hpp index e320e29b23c8..14ddc0be8d9f 100644 --- a/libs/core/algorithms/tests/unit/algorithms/count_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/count_tests.hpp @@ -101,7 +101,7 @@ void test_count_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::end(c)), int(0)) | hpx::count(ex_policy.on(exec))); - std::int64_t num_items = hpx::get<0>(*snd_result); + std::int64_t num_items = hpx::get<0>(snd_result.value()); HPX_TEST_EQ(num_items, static_cast(find_count)); } diff --git a/libs/core/algorithms/tests/unit/algorithms/countif_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/countif_tests.hpp index ffc94168326d..02dd3d3b69d0 100644 --- a/libs/core/algorithms/tests/unit/algorithms/countif_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/countif_tests.hpp @@ -108,7 +108,7 @@ void test_count_if_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) smaller_than_50()) | hpx::count_if(ex_policy.on(exec))); - diff_type num_items = hpx::get<0>(*snd_result); + diff_type num_items = hpx::get<0>(snd_result.value()); HPX_TEST_EQ(num_items, 50u); } diff --git a/libs/core/algorithms/tests/unit/algorithms/ends_with_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/ends_with_sender.cpp index 55a842ae6f0e..2de3a0f632a1 100644 --- a/libs/core/algorithms/tests/unit/algorithms/ends_with_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/ends_with_sender.cpp @@ -61,7 +61,7 @@ void test_ends_with_sender( iterator(std::end(some_more_ints))) | hpx::ends_with(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(result); } @@ -73,7 +73,7 @@ void test_ends_with_sender( iterator(std::end(some_wrong_ints))) | hpx::ends_with(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(!result); } @@ -87,7 +87,7 @@ void test_ends_with_sender( iterator(std::end(some_ints))) | hpx::ends_with(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(!result); } } diff --git a/libs/core/algorithms/tests/unit/algorithms/equal_binary_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/equal_binary_tests.hpp index 80f2f36d7630..606d27a78f27 100644 --- a/libs/core/algorithms/tests/unit/algorithms/equal_binary_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/equal_binary_tests.hpp @@ -494,7 +494,7 @@ void test_equal_binary_sender( ex::just(iterator(std::begin(c1)), iterator(std::end(c1)), std::begin(c2), std::end(c2)) | hpx::equal(policy)); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::equal(std::begin(c1), std::end(c1), std::begin(c2)); @@ -511,7 +511,7 @@ void test_equal_binary_sender( ex::just(iterator(std::begin(c1)), iterator(std::end(c1)), std::begin(c2), std::end(c2)) | hpx::equal(policy)); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::equal(std::begin(c1), std::end(c1), std::begin(c2)); @@ -591,7 +591,7 @@ void test_equal_binary_edge_cases_sender( std::begin(c2), std::begin(c2)) | hpx::equal(policy)); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(result); } @@ -603,7 +603,7 @@ void test_equal_binary_edge_cases_sender( std::begin(c1), std::end(c1)) | hpx::equal(policy)); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(!result); } @@ -615,7 +615,7 @@ void test_equal_binary_edge_cases_sender( std::begin(c1), std::begin(c1)) | hpx::equal(policy)); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(!result); } @@ -627,7 +627,7 @@ void test_equal_binary_edge_cases_sender( std::begin(c1), std::begin(c1) + 2) | hpx::equal(policy)); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(!result); } diff --git a/libs/core/algorithms/tests/unit/algorithms/equal_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/equal_tests.hpp index 914bb592edba..ea8f43248601 100644 --- a/libs/core/algorithms/tests/unit/algorithms/equal_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/equal_tests.hpp @@ -132,7 +132,7 @@ void test_equal1_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(std::begin(c1), std::end(c1), std::begin(c2)) | hpx::equal(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::equal(std::begin(c1), std::end(c1), std::begin(c2)); @@ -150,7 +150,7 @@ void test_equal1_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) iterator(std::end(c1)), std::begin(c2)) | hpx::equal(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::equal(std::begin(c1), std::end(c1), std::begin(c2)); @@ -167,7 +167,7 @@ void test_equal1_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) iterator(std::begin(c1)), std::begin(c2)) | hpx::equal(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(result); } @@ -322,7 +322,7 @@ void test_equal2_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) std::begin(c2), std::equal_to<>()) | hpx::equal(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::equal(std::begin(c1), std::end(c1), std::begin(c2)); @@ -340,7 +340,7 @@ void test_equal2_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) std::begin(c2), std::equal_to<>()) | hpx::equal(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::equal(std::begin(c1), std::end(c1), std::begin(c2)); @@ -357,7 +357,7 @@ void test_equal2_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) std::begin(c2), std::equal_to<>()) | hpx::equal(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(result); } diff --git a/libs/core/algorithms/tests/unit/algorithms/find_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/find_tests.hpp index d8cefc74deb8..845cf4b7cdff 100644 --- a/libs/core/algorithms/tests/unit/algorithms/find_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/find_tests.hpp @@ -181,7 +181,7 @@ void test_find_explicit_sender_direct_async(Policy l, ExPolicy&& p, IteratorTag) auto snd_result = tt::sync_wait(hpx::find( p.on(exec), iterator(std::begin(c)), iterator(std::end(c)), int(1))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); // create iterator at position of value to be found base_iterator test_index = diff --git a/libs/core/algorithms/tests/unit/algorithms/findend_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/findend_sender.cpp index b576c478f96e..c9b65dd39db8 100644 --- a/libs/core/algorithms/tests/unit/algorithms/findend_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/findend_sender.cpp @@ -37,11 +37,11 @@ void find_end_sender_test2() int hpx_main(hpx::program_options::variables_map& vm) { - unsigned int seed = (unsigned int) std::time(nullptr); if (vm.count("seed")) seed = vm["seed"].as(); std::cout << "using seed: " << seed << std::endl; + gen.seed(seed); std::srand(seed); find_end_sender_test1(); diff --git a/libs/core/algorithms/tests/unit/algorithms/findend_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/findend_tests.hpp index 8ac1ff31cd1e..3778f1c81fd3 100644 --- a/libs/core/algorithms/tests/unit/algorithms/findend_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/findend_tests.hpp @@ -30,6 +30,38 @@ std::mt19937 gen(seed); std::uniform_int_distribution<> dis(3, 102); std::uniform_int_distribution<> dist(7, 106); +/// Shared by \a test_find_end1_sender (main case) and \a test_find_end2_sender. +/// Keeps \c sync_wait formatting consistent so GCC + libstdexec deduce the +/// completion type reliably (same pattern as the first block in +/// test_find_end1_sender). +template +void find_end_sender_pipe_compare(std::vector const& c, int const* hf, + int const* hl, LnPolicy ln_policy, ExPolicy&& ex_policy) +{ + static_assert(hpx::is_async_execution_policy_v, + "hpx::is_async_execution_policy_v"); + + using base_iterator = std::vector::const_iterator; + using iterator = test::test_iterator; + + namespace ex = hpx::execution::experimental; + namespace tt = hpx::this_thread::experimental; + using scheduler_t = ex::thread_pool_policy_scheduler; + + auto exec = ex::explicit_scheduler_executor(scheduler_t(ln_policy)); + + auto snd_result = tt::sync_wait( + ex::just(iterator(std::begin(c)), iterator(std::end(c)), hf, hl) | + hpx::find_end(HPX_FORWARD(ExPolicy, ex_policy).on(exec))); + + iterator index = hpx::get<0>(snd_result.value()); + + iterator test_index = + std::find_end(iterator(std::begin(c)), iterator(std::end(c)), hf, hl); + + HPX_TEST(index == test_index); +} + template void test_find_end1(IteratorTag) { @@ -104,21 +136,10 @@ void test_find_end1_sender( int h[] = {1, 2}; - auto exec = ex::explicit_scheduler_executor(scheduler_t(ln_policy)); - - { - auto snd_result = tt::sync_wait( - ex::just(iterator(std::begin(c)), iterator(std::end(c)), - std::begin(h), std::end(h)) | - hpx::find_end(ex_policy.on(exec))); - - iterator index = hpx::get<0>(*snd_result); - - iterator test_index = std::find_end(iterator(std::begin(c)), - iterator(std::end(c)), std::begin(h), std::end(h)); + find_end_sender_pipe_compare( + c, std::begin(h), std::end(h), ln_policy, ex_policy); - HPX_TEST(index == test_index); - } + auto exec = ex::explicit_scheduler_executor(scheduler_t(ln_policy)); { // edge case: first2 == end2 @@ -127,7 +148,7 @@ void test_find_end1_sender( ex::just(iterator(std::begin(c)), iterator(std::end(c)), std::begin(h), std::begin(h)) | hpx::find_end(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(iterator(std::end(c)) == result); } @@ -139,7 +160,7 @@ void test_find_end1_sender( ex::just(iterator(std::begin(c)), iterator(std::begin(c)), std::begin(h), std::end(h)) | hpx::find_end(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(iterator(std::begin(c)) == result); } @@ -232,13 +253,6 @@ void test_find_end2_sender( static_assert(hpx::is_async_execution_policy_v, "hpx::is_async_execution_policy_v"); - using base_iterator = std::vector::iterator; - using iterator = test::test_iterator; - - namespace ex = hpx::execution::experimental; - namespace tt = hpx::this_thread::experimental; - using scheduler_t = ex::thread_pool_policy_scheduler; - std::vector c(10007); // fill vector with random values about 2 std::fill(std::begin(c), std::end(c), dis(gen)); @@ -250,18 +264,8 @@ void test_find_end2_sender( int h[] = {1, 2}; - auto exec = ex::explicit_scheduler_executor(scheduler_t(ln_policy)); - - auto snd_result = - tt::sync_wait(ex::just(iterator(std::begin(c)), iterator(std::end(c)), - std::begin(h), std::end(h)) | - hpx::find_end(ex_policy.on(exec))); - iterator index = hpx::get<0>(*snd_result); - - iterator test_index = std::find_end(iterator(std::begin(c)), - iterator(std::end(c)), std::begin(h), std::end(h)); - - HPX_TEST(index == test_index); + find_end_sender_pipe_compare(c, std::begin(h), std::end(h), + ln_policy, HPX_FORWARD(ExPolicy, ex_policy)); } template diff --git a/libs/core/algorithms/tests/unit/algorithms/findfirstof_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/findfirstof_tests.hpp index d53295a090e1..ea633c076f0b 100644 --- a/libs/core/algorithms/tests/unit/algorithms/findfirstof_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/findfirstof_tests.hpp @@ -106,7 +106,7 @@ void test_find_first_of_sender( std::begin(h), std::end(h)) | hpx::find_first_of(ex_policy.on(exec))); - iterator index = hpx::get<0>(*snd_result); + iterator index = hpx::get<0>(snd_result.value()); base_iterator test_index = std::begin(c) + find_first_of_pos; diff --git a/libs/core/algorithms/tests/unit/algorithms/findif_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/findif_tests.hpp index 8b94751a2393..5b4f5e2d486b 100644 --- a/libs/core/algorithms/tests/unit/algorithms/findif_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/findif_tests.hpp @@ -96,7 +96,7 @@ void test_find_if_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) [](auto v) { return v == int(1); }) | hpx::find_if(ex_policy.on(exec))); - iterator index = hpx::get<0>(*snd_result); + iterator index = hpx::get<0>(snd_result.value()); base_iterator test_index = std::begin(c) + static_cast(c.size() / 2); diff --git a/libs/core/algorithms/tests/unit/algorithms/findifnot_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/findifnot_tests.hpp index be95b1a2ba79..09bebca8f1ab 100644 --- a/libs/core/algorithms/tests/unit/algorithms/findifnot_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/findifnot_tests.hpp @@ -96,7 +96,7 @@ void test_find_if_not_sender( [](auto v) { return v != int(1); }) | hpx::find_if_not(ex_policy.on(exec))); - iterator index = hpx::get<0>(*snd_result); + iterator index = hpx::get<0>(snd_result.value()); base_iterator test_index = std::begin(c) + static_cast(c.size() / 2); diff --git a/libs/core/algorithms/tests/unit/algorithms/foreach_scheduler.cpp b/libs/core/algorithms/tests/unit/algorithms/foreach_scheduler.cpp index 7b3752d2cf35..67a932fd502b 100644 --- a/libs/core/algorithms/tests/unit/algorithms/foreach_scheduler.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/foreach_scheduler.cpp @@ -98,7 +98,6 @@ void test_for_each_execute_on_async(Policy l, ExPolicy&& policy, IteratorTag) auto result = tt::sync_wait(hpx::for_each( ex::execute_on(scheduler_t(l), std::forward(policy)), iterator(std::begin(c)), iterator(std::end(c)), f)); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) HPX_TEST(hpx::get<0>(*result) == iterator(std::end(c))); // verify values @@ -134,7 +133,6 @@ void test_for_each_execute_on_sender(Policy l, ExPolicy&& policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::end(c)), f) | hpx::for_each( ex::execute_on(scheduler_t(l), std::forward(policy)))); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) HPX_TEST(hpx::get<0>(*result) == iterator(std::end(c))); // verify values diff --git a/libs/core/algorithms/tests/unit/algorithms/foreach_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/foreach_sender.cpp index 2944aeb5e5c5..5fee8c62ac96 100644 --- a/libs/core/algorithms/tests/unit/algorithms/foreach_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/foreach_sender.cpp @@ -76,7 +76,7 @@ void test_for_each_explicit_sender_direct_async( auto snd_result = tt::sync_wait(hpx::for_each( policy.on(exec), iterator(std::begin(c)), iterator(std::end(c)), f)); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result == iterator(std::end(c))); @@ -114,7 +114,7 @@ void test_for_each_explicit_sender(Policy l, ExPolicy&& policy, IteratorTag) auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::end(c)), f) | hpx::for_each(policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result == iterator(std::end(c))); diff --git a/libs/core/algorithms/tests/unit/algorithms/foreach_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/foreach_tests.hpp index 060019e129b6..2bfc05cd540d 100644 --- a/libs/core/algorithms/tests/unit/algorithms/foreach_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/foreach_tests.hpp @@ -396,7 +396,7 @@ void test_for_each_n_sender( tt::sync_wait(ex::just(iterator(std::begin(c)), c.size(), set_42()) | hpx::for_each_n(ex_policy.on(exec))); - iterator result = hpx::get<0>(*snd_result); + iterator result = hpx::get<0>(snd_result.value()); iterator end = iterator(std::end(c)); HPX_TEST(result == end); diff --git a/libs/core/algorithms/tests/unit/algorithms/includes_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/includes_sender.cpp index 026c7c641e43..0d2af2d1a248 100644 --- a/libs/core/algorithms/tests/unit/algorithms/includes_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/includes_sender.cpp @@ -51,8 +51,10 @@ void test_includes1_sender( HPX_TEST_LTE(start, end); - base_iterator start_it = std::next(std::begin(c1), start); - base_iterator end_it = std::next(std::begin(c1), end); + base_iterator start_it = + std::next(std::begin(c1), static_cast(start)); + base_iterator end_it = + std::next(std::begin(c1), static_cast(end)); { auto exec = ex::explicit_scheduler_executor(scheduler_t(ln_policy)); @@ -62,7 +64,7 @@ void test_includes1_sender( iterator(std::end(c1)), start_it, end_it) | hpx::includes(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::includes(std::begin(c1), std::end(c1), start_it, end_it); @@ -93,7 +95,7 @@ void test_includes1_sender( std::begin(c2), std::end(c2)) | hpx::includes(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::includes( std::begin(c1), std::end(c1), std::begin(c2), std::end(c2)); @@ -130,8 +132,10 @@ void test_includes2_sender( HPX_TEST_LTE(start, end); - base_iterator start_it = std::next(std::begin(c1), start); - base_iterator end_it = std::next(std::begin(c1), end); + base_iterator start_it = + std::next(std::begin(c1), static_cast(start)); + base_iterator end_it = + std::next(std::begin(c1), static_cast(end)); { auto exec = ex::explicit_scheduler_executor(scheduler_t(ln_policy)); @@ -141,7 +145,7 @@ void test_includes2_sender( end_it, std::less()) | hpx::includes(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::includes(std::begin(c1), std::end(c1), start_it, end_it, std::less()); @@ -172,7 +176,7 @@ void test_includes2_sender( std::begin(c2), std::end(c2), std::less()) | hpx::includes(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::includes(std::begin(c1), std::end(c1), std::begin(c2), std::end(c2), std::less()); @@ -210,7 +214,7 @@ void test_includes_edge_cases_sender( std::begin(c), std::end(c), std::less{}) | hpx::includes(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::includes(std::begin(c), std::begin(c), std::begin(c), std::end(c), std::less{}); @@ -227,7 +231,7 @@ void test_includes_edge_cases_sender( std::begin(c), std::begin(c), std::less{}) | hpx::includes(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::includes(std::begin(c), std::end(c), std::begin(c), std::begin(c), std::less{}); @@ -244,7 +248,7 @@ void test_includes_edge_cases_sender( std::begin(c), std::begin(c), std::less{}) | hpx::includes(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool expected = std::includes(std::begin(c), std::begin(c), std::begin(c), std::begin(c), std::less{}); diff --git a/libs/core/algorithms/tests/unit/algorithms/is_heap_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/is_heap_tests.hpp index 9a931d5e0a8e..e013b06f6a4c 100644 --- a/libs/core/algorithms/tests/unit/algorithms/is_heap_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/is_heap_tests.hpp @@ -170,7 +170,7 @@ void test_is_heap_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::end(c))) | hpx::is_heap(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool solution = std::is_heap(std::begin(c), std::end(c)); @@ -184,7 +184,7 @@ void test_is_heap_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::begin(c))) | hpx::is_heap(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool solution = std::is_heap(std::begin(c), std::begin(c)); @@ -199,7 +199,7 @@ void test_is_heap_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(++std::begin(c))) | hpx::is_heap(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); bool solution = std::is_heap(std::begin(c), ++std::begin(c)); @@ -235,7 +235,7 @@ void test_is_heap_until_sender( ex::just(iterator(std::begin(c)), iterator(std::end(c))) | hpx::is_heap_until(ex_policy.on(exec))); - iterator result = hpx::get<0>(*snd_result); + iterator result = hpx::get<0>(snd_result.value()); auto solution = std::is_heap_until(std::begin(c), std::end(c)); @@ -249,7 +249,7 @@ void test_is_heap_until_sender( ex::just(iterator(std::begin(c)), iterator(std::begin(c))) | hpx::is_heap_until(ex_policy.on(exec))); - iterator result = hpx::get<0>(*snd_result); + iterator result = hpx::get<0>(snd_result.value()); auto solution = std::is_heap_until(std::begin(c), std::begin(c)); @@ -264,7 +264,7 @@ void test_is_heap_until_sender( ex::just(iterator(std::begin(c)), iterator(++std::begin(c))) | hpx::is_heap_until(ex_policy.on(exec))); - iterator result = hpx::get<0>(*snd_result); + iterator result = hpx::get<0>(snd_result.value()); auto solution = std::is_heap_until(std::begin(c), ++std::begin(c)); diff --git a/libs/core/algorithms/tests/unit/algorithms/is_partitioned_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/is_partitioned_sender.cpp index 5daddb84e6e5..a07d3e99fc3b 100644 --- a/libs/core/algorithms/tests/unit/algorithms/is_partitioned_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/is_partitioned_sender.cpp @@ -55,7 +55,7 @@ void test_is_partitioned_sender( [](std::size_t n) { return n % 2 == 0; }) | hpx::is_partitioned(ex_policy.on(exec))); - bool parted = hpx::get<0>(*snd_result); + bool parted = hpx::get<0>(snd_result.value()); HPX_TEST(parted); } @@ -67,7 +67,7 @@ void test_is_partitioned_sender( [](std::size_t) { return true; }) | hpx::is_partitioned(ex_policy.on(exec))); - auto parted = hpx::get<0>(*snd_result); + auto parted = hpx::get<0>(snd_result.value()); HPX_TEST(parted); } @@ -79,7 +79,7 @@ void test_is_partitioned_sender( [](std::size_t) { return true; }) | hpx::is_partitioned(ex_policy.on(exec))); - auto parted = hpx::get<0>(*snd_result); + auto parted = hpx::get<0>(snd_result.value()); HPX_TEST(parted); } diff --git a/libs/core/algorithms/tests/unit/algorithms/is_sorted_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/is_sorted_tests.hpp index 129767c39939..9485726ff446 100644 --- a/libs/core/algorithms/tests/unit/algorithms/is_sorted_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/is_sorted_tests.hpp @@ -448,7 +448,7 @@ void test_is_sorted_sender( ex::just(iterator(std::begin(c)), iterator(std::end(c))) | hpx::is_sorted(ex_policy.on(exec))); - bool is_ordered = hpx::get<0>(*snd_result); + bool is_ordered = hpx::get<0>(snd_result.value()); HPX_TEST(is_ordered); } @@ -459,7 +459,7 @@ void test_is_sorted_sender( ex::just(iterator(std::begin(c)), iterator(std::begin(c))) | hpx::is_sorted(ex_policy.on(exec))); - bool is_ordered = hpx::get<0>(*snd_result); + bool is_ordered = hpx::get<0>(snd_result.value()); HPX_TEST(is_ordered); } @@ -470,7 +470,7 @@ void test_is_sorted_sender( ex::just(iterator(std::begin(c)), iterator(++std::begin(c))) | hpx::is_sorted(ex_policy.on(exec))); - bool is_ordered = hpx::get<0>(*snd_result); + bool is_ordered = hpx::get<0>(snd_result.value()); HPX_TEST(is_ordered); } diff --git a/libs/core/algorithms/tests/unit/algorithms/is_sorted_until_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/is_sorted_until_sender.cpp index 07109539ff3a..bc973a4f4197 100644 --- a/libs/core/algorithms/tests/unit/algorithms/is_sorted_until_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/is_sorted_until_sender.cpp @@ -49,7 +49,7 @@ void test_is_sorted_until_sender( ex::just(iterator(std::begin(c)), iterator(std::end(c))) | hpx::is_sorted_until(ex_policy.on(exec))); - iterator until = hpx::get<0>(*snd_result); + iterator until = hpx::get<0>(snd_result.value()); HPX_TEST(until == iterator(std::end(c))); } @@ -60,7 +60,7 @@ void test_is_sorted_until_sender( ex::just(iterator(std::begin(c)), iterator(std::begin(c))) | hpx::is_sorted_until(ex_policy.on(exec))); - iterator until = hpx::get<0>(*snd_result); + iterator until = hpx::get<0>(snd_result.value()); HPX_TEST(until == iterator(std::begin(c))); } @@ -71,7 +71,7 @@ void test_is_sorted_until_sender( ex::just(iterator(std::begin(c)), iterator(++std::begin(c))) | hpx::is_sorted_until(ex_policy.on(exec))); - iterator until = hpx::get<0>(*snd_result); + iterator until = hpx::get<0>(snd_result.value()); HPX_TEST(until == iterator(++std::begin(c))); } diff --git a/libs/core/algorithms/tests/unit/algorithms/lexicographical_compare_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/lexicographical_compare_sender.cpp index b62623645164..551ff9b36742 100644 --- a/libs/core/algorithms/tests/unit/algorithms/lexicographical_compare_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/lexicographical_compare_sender.cpp @@ -21,7 +21,7 @@ #include "test_utils.hpp" //////////////////////////////////////////////////////////////////////////// -int seed = std::random_device{}(); +int seed = static_cast(std::random_device{}()); std::mt19937 gen(seed); template @@ -53,7 +53,7 @@ void test_lexicographical_compare_sender( std::begin(d), std::end(d)) | hpx::lexicographical_compare(ex_policy.on(exec))); - bool res = hpx::get<0>(*snd_result); + bool res = hpx::get<0>(snd_result.value()); HPX_TEST(!res); } @@ -65,7 +65,7 @@ void test_lexicographical_compare_sender( ex::just(iterator(std::begin(c)), iterator(std::begin(c)), std::begin(d), std::end(d)) | hpx::lexicographical_compare(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(result); } @@ -77,7 +77,7 @@ void test_lexicographical_compare_sender( ex::just(iterator(std::begin(c)), iterator(std::end(c)), std::begin(d), std::begin(d)) | hpx::lexicographical_compare(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(!result); } @@ -89,7 +89,7 @@ void test_lexicographical_compare_sender( ex::just(iterator(std::begin(c)), iterator(std::begin(c)), std::begin(d), std::begin(d)) | hpx::lexicographical_compare(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); HPX_TEST(!result); } diff --git a/libs/core/algorithms/tests/unit/algorithms/max_element_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/max_element_sender.cpp index 6d438d61e926..8dc252f86fef 100644 --- a/libs/core/algorithms/tests/unit/algorithms/max_element_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/max_element_sender.cpp @@ -46,7 +46,7 @@ void test_max_element_sender( tt::sync_wait(ex::just(iterator(std::begin(c)), iterator(end), std::less()) | hpx::max_element(ex_policy.on(exec))); - iterator result = hpx::get<0>(*snd_result); + iterator result = hpx::get<0>(snd_result.value()); HPX_TEST(result != end); @@ -60,7 +60,7 @@ void test_max_element_sender( auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::end(c))) | hpx::max_element(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result != end); @@ -75,7 +75,7 @@ void test_max_element_sender( auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::begin(c))) | hpx::max_element(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result == iterator(std::begin(c))); } diff --git a/libs/core/algorithms/tests/unit/algorithms/min_element_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/min_element_sender.cpp index 313c6fd8fb1d..339c70258fca 100644 --- a/libs/core/algorithms/tests/unit/algorithms/min_element_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/min_element_sender.cpp @@ -47,7 +47,7 @@ void test_min_element_sender( tt::sync_wait(ex::just(iterator(std::begin(c)), iterator(end), std::less()) | hpx::min_element(ex_policy.on(exec))); - iterator result = hpx::get<0>(*snd_result); + iterator result = hpx::get<0>(snd_result.value()); HPX_TEST(result != end); @@ -61,7 +61,7 @@ void test_min_element_sender( auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::end(c))) | hpx::min_element(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result != end); @@ -76,7 +76,7 @@ void test_min_element_sender( auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::begin(c))) | hpx::min_element(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result == iterator(std::begin(c))); } diff --git a/libs/core/algorithms/tests/unit/algorithms/minmax_element_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/minmax_element_sender.cpp index 8a82a3eacbaf..9834bb0a4450 100644 --- a/libs/core/algorithms/tests/unit/algorithms/minmax_element_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/minmax_element_sender.cpp @@ -48,7 +48,7 @@ void test_minmax_element_sender( tt::sync_wait(ex::just(iterator(std::begin(c)), iterator(end), std::less()) | hpx::minmax_element(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result.min != end && result.max != end); @@ -64,7 +64,7 @@ void test_minmax_element_sender( auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::end(c))) | hpx::minmax_element(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result.min != end && result.max != end); @@ -81,7 +81,7 @@ void test_minmax_element_sender( auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::begin(c))) | hpx::minmax_element(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result.min == iterator(std::begin(c)) && result.max == iterator(std::begin(c))); diff --git a/libs/core/algorithms/tests/unit/algorithms/mismatch_binary_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/mismatch_binary_tests.hpp index b5de66864ce7..639a7115fe9f 100644 --- a/libs/core/algorithms/tests/unit/algorithms/mismatch_binary_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/mismatch_binary_tests.hpp @@ -512,7 +512,7 @@ void test_mismatch_binary1_sender( auto snd_result = tt::sync_wait(ex::just(begin1, end1, std::begin(c2), std::end(c2)) | hpx::mismatch(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); // verify values HPX_TEST_EQ( @@ -528,7 +528,7 @@ void test_mismatch_binary1_sender( auto snd_result = tt::sync_wait(ex::just(begin1, end1, std::begin(c2), std::end(c2)) | hpx::mismatch(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); // verify values HPX_TEST_EQ( @@ -567,7 +567,7 @@ void test_mismatch_binary2_sender( auto snd_result = tt::sync_wait(ex::just(begin1, end1, std::begin(c2), std::end(c2), std::equal_to<>()) | hpx::mismatch(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); // verify values HPX_TEST_EQ( @@ -583,7 +583,7 @@ void test_mismatch_binary2_sender( auto snd_result = tt::sync_wait(ex::just(begin1, end1, std::begin(c2), std::end(c2), std::equal_to<>()) | hpx::mismatch(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); // verify values HPX_TEST_EQ( @@ -599,7 +599,7 @@ void test_mismatch_binary2_sender( ex::just(iterator(std::begin(c1)), iterator(std::begin(c1)), std::begin(c2), std::end(c2), std::equal_to<>()) | hpx::mismatch(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); // verify values HPX_TEST(result.first.base() == std::begin(c1)); diff --git a/libs/core/algorithms/tests/unit/algorithms/mismatch_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/mismatch_tests.hpp index 3ef0e1be36fe..4b2881f15286 100644 --- a/libs/core/algorithms/tests/unit/algorithms/mismatch_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/mismatch_tests.hpp @@ -509,7 +509,7 @@ void test_mismatch_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) { auto snd_result = tt::sync_wait(ex::just(begin1, end1, std::begin(c2)) | hpx::mismatch(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); // verify values HPX_TEST_EQ( @@ -526,7 +526,7 @@ void test_mismatch_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) auto snd_result = tt::sync_wait(ex::just(begin1, end1, std::begin(c2)) | hpx::mismatch(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); // verify values HPX_TEST_EQ( @@ -544,7 +544,7 @@ void test_mismatch_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) tt::sync_wait(ex::just(iterator(std::begin(c1)), iterator(std::begin(c1)), std::begin(c2)) | hpx::mismatch(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); // verify values HPX_TEST(result.first.base() == std::begin(c1)); diff --git a/libs/core/algorithms/tests/unit/algorithms/none_of_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/none_of_sender.cpp index 6e54a1c19fa6..8846f44ba688 100644 --- a/libs/core/algorithms/tests/unit/algorithms/none_of_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/none_of_sender.cpp @@ -4,6 +4,11 @@ // 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) +#include + +// Clang 20 and earlier currently ICE while compiling this file. +#if !defined(HPX_CLANG_VERSION) || (HPX_CLANG_VERSION / 10000) > 20 + #include #include @@ -62,3 +67,12 @@ int main(int argc, char* argv[]) return hpx::util::report_errors(); } + +#else + +int main() +{ + return 0; +} + +#endif diff --git a/libs/core/algorithms/tests/unit/algorithms/none_of_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/none_of_tests.hpp index ba395895283b..9b6dabb635a9 100644 --- a/libs/core/algorithms/tests/unit/algorithms/none_of_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/none_of_tests.hpp @@ -94,7 +94,7 @@ void test_none_of_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::end(c)), [](auto v) { return v != 0; }) | hpx::none_of(ex_policy.on(exec))); - bool result = hpx::get<0>(*snd_result); + bool result = hpx::get<0>(snd_result.value()); // verify values bool expected = std::none_of( diff --git a/libs/core/algorithms/tests/unit/algorithms/partial_sort_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/partial_sort_sender.cpp index 97942665ed97..ababd9f2a7b9 100644 --- a/libs/core/algorithms/tests/unit/algorithms/partial_sort_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/partial_sort_sender.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -55,7 +56,8 @@ void test_partial_sort_sender( auto exec = ex::explicit_scheduler_executor(scheduler_t(ln_policy)); tt::sync_wait( - ex::just(iterator(std::begin(B)), iterator(std::begin(B) + i), + ex::just(iterator(std::begin(B)), + iterator(std::begin(B) + static_cast(i)), iterator(std::end(B)), compare_t{}) | hpx::partial_sort(ex_policy.on(exec))); diff --git a/libs/core/algorithms/tests/unit/algorithms/reduce_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/reduce_tests.hpp index a3a2707c67ac..b0b900713b44 100644 --- a/libs/core/algorithms/tests/unit/algorithms/reduce_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/reduce_tests.hpp @@ -370,7 +370,7 @@ void test_reduce_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::end(c)), val, op) | hpx::reduce(ex_policy.on(exec))); - int result = hpx::get<0>(*snd_result); + int result = hpx::get<0>(snd_result.value()); // verify values int expected = std::accumulate(std::begin(c), std::end(c), val, op); @@ -381,7 +381,7 @@ void test_reduce_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) auto snd_result = tt::sync_wait(ex::just(iterator(std::begin(c)), iterator(std::begin(c)), val, op) | hpx::reduce(ex_policy.on(exec))); - int result = hpx::get<0>(*snd_result); + int result = hpx::get<0>(snd_result.value()); HPX_TEST_EQ(result, val); } diff --git a/libs/core/algorithms/tests/unit/algorithms/remove.cpp b/libs/core/algorithms/tests/unit/algorithms/remove.cpp index c9ee10a61e78..19806e2fe731 100644 --- a/libs/core/algorithms/tests/unit/algorithms/remove.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/remove.cpp @@ -42,6 +42,7 @@ int hpx_main(hpx::program_options::variables_map& vm) seed = vm["seed"].as(); std::cout << "using seed: " << seed << std::endl; + remove_tests_seed_rng(seed); std::srand(seed); remove_test(); diff --git a/libs/core/algorithms/tests/unit/algorithms/remove1.cpp b/libs/core/algorithms/tests/unit/algorithms/remove1.cpp index 297c7874ff1c..4fccb76f84b1 100644 --- a/libs/core/algorithms/tests/unit/algorithms/remove1.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/remove1.cpp @@ -42,6 +42,7 @@ int hpx_main(hpx::program_options::variables_map& vm) seed = vm["seed"].as(); std::cout << "using seed: " << seed << std::endl; + remove_tests_seed_rng(seed); std::srand(seed); remove_test(); diff --git a/libs/core/algorithms/tests/unit/algorithms/remove2.cpp b/libs/core/algorithms/tests/unit/algorithms/remove2.cpp index d556a08e01a0..e6e63a756d2a 100644 --- a/libs/core/algorithms/tests/unit/algorithms/remove2.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/remove2.cpp @@ -42,6 +42,7 @@ int hpx_main(hpx::program_options::variables_map& vm) seed = vm["seed"].as(); std::cout << "using seed: " << seed << std::endl; + remove_tests_seed_rng(seed); std::srand(seed); remove_test(); diff --git a/libs/core/algorithms/tests/unit/algorithms/remove_if.cpp b/libs/core/algorithms/tests/unit/algorithms/remove_if.cpp index 2ee33ff8f5b8..4f5b0cee81de 100644 --- a/libs/core/algorithms/tests/unit/algorithms/remove_if.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/remove_if.cpp @@ -45,6 +45,7 @@ int hpx_main(hpx::program_options::variables_map& vm) seed = vm["seed"].as(); std::cout << "using seed: " << seed << std::endl; + remove_tests_seed_rng(seed); std::srand(seed); remove_test(); diff --git a/libs/core/algorithms/tests/unit/algorithms/remove_if1.cpp b/libs/core/algorithms/tests/unit/algorithms/remove_if1.cpp index 316b18b0546b..1b67ea5667bf 100644 --- a/libs/core/algorithms/tests/unit/algorithms/remove_if1.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/remove_if1.cpp @@ -42,6 +42,7 @@ int hpx_main(hpx::program_options::variables_map& vm) seed = vm["seed"].as(); std::cout << "using seed: " << seed << std::endl; + remove_tests_seed_rng(seed); std::srand(seed); remove_test(); diff --git a/libs/core/algorithms/tests/unit/algorithms/remove_if_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/remove_if_sender.cpp index 1a72765bfcfe..814dbd255939 100644 --- a/libs/core/algorithms/tests/unit/algorithms/remove_if_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/remove_if_sender.cpp @@ -31,6 +31,7 @@ int hpx_main(hpx::program_options::variables_map& vm) seed = vm["seed"].as(); std::cout << "using seed: " << seed << std::endl; + remove_tests_seed_rng(seed); std::srand(seed); remove_if_sender_test(); diff --git a/libs/core/algorithms/tests/unit/algorithms/remove_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/remove_sender.cpp index 35848610024f..b8c8b650b5a3 100644 --- a/libs/core/algorithms/tests/unit/algorithms/remove_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/remove_sender.cpp @@ -31,6 +31,7 @@ int hpx_main(hpx::program_options::variables_map& vm) seed = vm["seed"].as(); std::cout << "using seed: " << seed << std::endl; + remove_tests_seed_rng(seed); std::srand(seed); remove_sender_test(); diff --git a/libs/core/algorithms/tests/unit/algorithms/remove_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/remove_tests.hpp index 1d86e6d6f236..6854a6019f77 100644 --- a/libs/core/algorithms/tests/unit/algorithms/remove_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/remove_tests.hpp @@ -25,8 +25,16 @@ #include "test_utils.hpp" /////////////////////////////////////////////////////////////////////////////// -unsigned int seed = std::random_device{}(); -std::mt19937 g(seed); +// Deterministic default; mains call remove_tests_seed_rng so --seed matches +// std::srand and this generator (random_fill / user_defined_type). +inline unsigned int remove_test_rng_seed = 4242424242u; +inline std::mt19937 g(remove_test_rng_seed); + +inline void remove_tests_seed_rng(unsigned int s) +{ + remove_test_rng_seed = s; + g.seed(s); +} struct throw_always { @@ -710,7 +718,7 @@ void test_remove_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) using scheduler_t = ex::thread_pool_policy_scheduler; std::size_t rand_base = g(); - std::size_t value = rand_base + 2; + int value = static_cast(rand_base + 2); std::size_t const size = 10007; std::vector c(size), d; @@ -723,7 +731,7 @@ void test_remove_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::end(c)), value) | hpx::remove(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); auto solution = std::remove(std::begin(d), std::end(d), value); @@ -748,7 +756,9 @@ void test_remove_if_sender( using scheduler_t = ex::thread_pool_policy_scheduler; std::size_t rand_base = g(); - auto pred = [rand_base](int const a) -> bool { return a == rand_base; }; + auto pred = [rand_base](int const a) -> bool { + return static_cast(a) == rand_base; + }; std::size_t const size = 10007; std::vector c(size), d; @@ -760,7 +770,7 @@ void test_remove_if_sender( auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::end(c)), pred) | hpx::remove_if(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); auto solution = std::remove_if(std::begin(d), std::end(d), pred); diff --git a/libs/core/algorithms/tests/unit/algorithms/search_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/search_sender.cpp index a5578caf44dc..735e05156d4a 100644 --- a/libs/core/algorithms/tests/unit/algorithms/search_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/search_sender.cpp @@ -48,7 +48,7 @@ void test_search_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::end(c)), std::begin(h), std::end(h)) | hpx::search(ex_policy.on(exec))); - iterator index = hpx::get<0>(*snd_result); + iterator index = hpx::get<0>(snd_result.value()); base_iterator test_index = std::begin(c) + static_cast(c.size() / 2); @@ -63,7 +63,7 @@ void test_search_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::end(c)), std::begin(h), std::begin(h)) | hpx::search(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); auto expected = std::search(iterator(std::begin(c)), iterator(std::end(c)), std::begin(h), std::begin(h)); @@ -79,7 +79,7 @@ void test_search_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(c)), iterator(std::begin(c)), std::begin(h), std::begin(h)) | hpx::search(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); auto expected = std::search(iterator(std::begin(c)), iterator(std::begin(c)), std::begin(h), std::begin(h)); @@ -95,7 +95,7 @@ void test_search_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) ex::just(iterator(std::begin(h)), iterator(std::end(h)), std::begin(c), std::end(c)) | hpx::search(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); auto expected = std::search(iterator(std::begin(h)), iterator(std::end(h)), std::begin(c), std::end(c)); diff --git a/libs/core/algorithms/tests/unit/algorithms/starts_with_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/starts_with_sender.cpp index 94ffcac14430..15b767862588 100644 --- a/libs/core/algorithms/tests/unit/algorithms/starts_with_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/starts_with_sender.cpp @@ -59,7 +59,7 @@ void test_starts_with_sender( iterator(std::begin(some_more_ints)), iterator(std::end(some_more_ints))) | hpx::starts_with(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST_EQ(result, true); } @@ -73,7 +73,7 @@ void test_starts_with_sender( iterator(std::begin(some_wrong_ints)), iterator(std::end(some_wrong_ints))) | hpx::starts_with(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST_EQ(result, false); } diff --git a/libs/core/algorithms/tests/unit/algorithms/transform_binary2_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/transform_binary2_tests.hpp index fc1a9b00598f..83820f74fc66 100644 --- a/libs/core/algorithms/tests/unit/algorithms/transform_binary2_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/transform_binary2_tests.hpp @@ -380,7 +380,7 @@ void test_transform_binary2_sender( tt::sync_wait(ex::just(iterator(std::begin(c1)), iterator(std::end(c1)), std::begin(c2), std::end(c2), std::begin(d1), add()) | hpx::ranges::transform(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result.in1 == iterator(std::end(c1))); HPX_TEST(result.in2 == std::end(c2)); diff --git a/libs/core/algorithms/tests/unit/algorithms/transform_binary_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/transform_binary_tests.hpp index 4b50b8386ae0..4bae8f8afa07 100644 --- a/libs/core/algorithms/tests/unit/algorithms/transform_binary_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/transform_binary_tests.hpp @@ -369,7 +369,7 @@ void test_transform_binary_sender( tt::sync_wait(ex::just(iterator(std::begin(c1)), iterator(std::end(c1)), std::begin(c2), std::begin(d1), add()) | hpx::transform(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result == std::end(d1)); diff --git a/libs/core/algorithms/tests/unit/algorithms/transform_reduce_binary_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/transform_reduce_binary_tests.hpp index e71d29212788..e083eb201232 100644 --- a/libs/core/algorithms/tests/unit/algorithms/transform_reduce_binary_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/transform_reduce_binary_tests.hpp @@ -110,7 +110,7 @@ void test_transform_reduce_binary_sender( tt::sync_wait(ex::just(iterator(std::begin(c)), iterator(std::end(c)), std::begin(d), init) | hpx::transform_reduce(ex_policy.on(exec))); - int result = hpx::get<0>(*snd_result); + int result = hpx::get<0>(snd_result.value()); HPX_TEST_EQ(result, std::inner_product( @@ -124,7 +124,7 @@ void test_transform_reduce_binary_sender( tt::sync_wait(ex::just(iterator(std::begin(c)), iterator(std::begin(c)), std::begin(d), init) | hpx::transform_reduce(ex_policy.on(exec))); - int result = hpx::get<0>(*snd_result); + int result = hpx::get<0>(snd_result.value()); HPX_TEST_EQ(init, result); HPX_TEST_EQ(result, diff --git a/libs/core/algorithms/tests/unit/algorithms/transform_reduce_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/transform_reduce_sender.cpp index 01796a23a924..d4ef6337ca51 100644 --- a/libs/core/algorithms/tests/unit/algorithms/transform_reduce_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/transform_reduce_sender.cpp @@ -60,7 +60,7 @@ void test_transform_reduce_sender( ex::just(iterator(std::begin(c)), iterator(std::end(c)), init, reduce_op, convert_op) | hpx::transform_reduce(ex_policy.on(exec))); - result_type result = hpx::get<0>(*snd_result); + result_type result = hpx::get<0>(snd_result.value()); // verify values result_type expected = std::accumulate(std::begin(c), std::end(c), init, @@ -79,7 +79,7 @@ void test_transform_reduce_sender( ex::just(iterator(std::begin(c)), iterator(std::begin(c)), init, reduce_op, convert_op) | hpx::transform_reduce(ex_policy.on(exec))); - result_type result = hpx::get<0>(*snd_result); + result_type result = hpx::get<0>(snd_result.value()); // verify values result_type expected = std::accumulate(std::begin(c), std::begin(c), diff --git a/libs/core/algorithms/tests/unit/algorithms/transform_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/transform_tests.hpp index 43f0ef980416..6b3217954c4a 100644 --- a/libs/core/algorithms/tests/unit/algorithms/transform_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/transform_tests.hpp @@ -328,7 +328,7 @@ void test_transform_sender( tt::sync_wait(ex::just(iterator(std::begin(c)), iterator(std::end(c)), std::begin(d), add_one()) | hpx::transform(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); HPX_TEST(result == std::end(d)); diff --git a/libs/core/algorithms/tests/unit/algorithms/uninitialized_fill_sender.cpp b/libs/core/algorithms/tests/unit/algorithms/uninitialized_fill_sender.cpp index 227578f4a423..1d06ebdfa50b 100644 --- a/libs/core/algorithms/tests/unit/algorithms/uninitialized_fill_sender.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/uninitialized_fill_sender.cpp @@ -83,7 +83,6 @@ void test_uninitialized_fill_exception_sender( test::count_instances::instance_count.store(0); bool caught_exception = false; - bool returned_from_algorithm = false; try { tt::sync_wait( diff --git a/libs/core/algorithms/tests/unit/algorithms/unique_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/unique_tests.hpp index ef75d03851da..5295d502a454 100644 --- a/libs/core/algorithms/tests/unit/algorithms/unique_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/unique_tests.hpp @@ -534,7 +534,7 @@ void test_unique_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::end(c)), pred) | hpx::unique(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); auto solution = std::unique(std::begin(d), std::end(d), pred); @@ -550,7 +550,7 @@ void test_unique_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(std::begin(c)), pred) | hpx::unique(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); auto solution = std::unique(std::begin(d), std::begin(d), pred); @@ -566,7 +566,7 @@ void test_unique_sender(LnPolicy ln_policy, ExPolicy&& ex_policy, IteratorTag) auto snd_result = tt::sync_wait( ex::just(iterator(std::begin(c)), iterator(++std::begin(c)), pred) | hpx::unique(ex_policy.on(exec))); - auto result = hpx::get<0>(*snd_result); + auto result = hpx::get<0>(snd_result.value()); auto solution = std::unique(std::begin(d), ++std::begin(d), pred); diff --git a/libs/core/algorithms/tests/unit/container_algorithms/CMakeLists.txt b/libs/core/algorithms/tests/unit/container_algorithms/CMakeLists.txt index ef961a094b02..0cfdd3772a7b 100644 --- a/libs/core/algorithms/tests/unit/container_algorithms/CMakeLists.txt +++ b/libs/core/algorithms/tests/unit/container_algorithms/CMakeLists.txt @@ -139,12 +139,6 @@ if(HPX_WITH_CXX20_COROUTINES) set(tests ${tests} for_loop_range_generator) endif() -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - list(REMOVE_ITEM tests adjacentdifference_range_sender foreach_range - foreach_range_sender - ) -endif() - foreach(test ${tests}) set(sources ${test}.cpp) diff --git a/libs/core/async_cuda/include/hpx/async_cuda/transform_stream.hpp b/libs/core/async_cuda/include/hpx/async_cuda/transform_stream.hpp index ea86f87e58b4..892a61aa66ae 100644 --- a/libs/core/async_cuda/include/hpx/async_cuda/transform_stream.hpp +++ b/libs/core/async_cuda/include/hpx/async_cuda/transform_stream.hpp @@ -263,49 +263,65 @@ namespace hpx::cuda::experimental { using sender_concept = hpx::execution::experimental::sender_t; - template - struct invoke_function_transformation_helper + struct invoke_function_transformation_fn { - template - struct set_value_void_checked + template + consteval auto operator()() const noexcept { - using type = hpx::execution::experimental::set_value_t(T); - }; + static_assert(hpx::is_invocable_v, + "F not invocable with the value_types specified."); + + using result_type = + hpx::util::invoke_result_t; + + if constexpr (std::is_void_v) + { + return hpx::execution::experimental:: + completion_signatures< + hpx::execution::experimental::set_value_t(), + hpx::execution::experimental::set_error_t( + std::exception_ptr)>{}; + } + else + { + return hpx::execution::experimental:: + completion_signatures< + hpx::execution::experimental::set_value_t( + result_type), + hpx::execution::experimental::set_error_t( + std::exception_ptr)>{}; + } + } + }; - template - struct set_value_void_checked + struct default_set_error_fn + { + template + consteval auto operator()() const noexcept { - using type = hpx::execution::experimental::set_value_t(); - }; - - static_assert(hpx::is_invocable_v, - "F not invocable with the value_types specified."); - - using result_type = - hpx::util::invoke_result_t; - using set_value_result_type = - typename set_value_void_checked, - result_type>::type; - using type = - hpx::execution::experimental::completion_signatures< - set_value_result_type>; + return hpx::execution::experimental::completion_signatures< + hpx::execution::experimental::set_error_t(Err)>{}; + } }; - template - using invoke_function_transformation = - invoke_function_transformation_helper::type; - + // clang-format off template - static consteval auto get_completion_signatures() - -> hpx::execution::experimental:: - transform_completion_signatures_of, Env, - hpx::execution::experimental::completion_signatures< - hpx::execution::experimental::set_error_t( - std::exception_ptr)>, - invoke_function_transformation> + static consteval auto get_completion_signatures() noexcept + -> decltype(hpx::execution::experimental::transform_completion_signatures( + hpx::execution::experimental::completion_signatures_of_t< + S, Env>{}, + invoke_function_transformation_fn{}, + default_set_error_fn{}, + hpx::execution::experimental::ignore_completion{})) { - return {}; + return hpx::execution::experimental::transform_completion_signatures( + hpx::execution::experimental::completion_signatures_of_t< + S, Env>{}, + invoke_function_transformation_fn{}, + default_set_error_fn{}, + hpx::execution::experimental::ignore_completion{}); } + // clang-format on constexpr auto get_env() const noexcept { 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 4559850fa782..1fdf6c31c17a 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 @@ -144,60 +144,60 @@ namespace hpx::mpi::experimental { using is_sender = void; using sender_concept = hpx::execution::experimental::sender_t; - template - struct invoke_function_transformation_helper + struct invoke_function_transformation_fn { - template - struct set_value_void_checked + template + consteval auto operator()() const noexcept { - using type = hpx::execution::experimental::set_value_t(T); - }; + static_assert(hpx::is_invocable_v, + "F not invocable with the value_types specified."); + + using result_type = + hpx::util::invoke_result_t; + + if constexpr (std::is_void_v) + { + return hpx::execution::experimental:: + completion_signatures< + hpx::execution::experimental::set_value_t()>{}; + } + else + { + return hpx::execution::experimental:: + completion_signatures< + hpx::execution::experimental::set_value_t( + result_type)>{}; + } + } + }; - template - struct set_value_void_checked + struct default_set_error_fn + { + template + consteval auto operator()() const noexcept { - using type = hpx::execution::experimental::set_value_t(); - }; - - static_assert(hpx::is_invocable_v, - "F not invocable with the value_types specified."); - - using result_type = - hpx::util::invoke_result_t; - using set_value_result_type = - typename set_value_void_checked, - result_type>::type; - using type = - hpx::execution::experimental::completion_signatures< - set_value_result_type>; + return hpx::execution::experimental::completion_signatures< + hpx::execution::experimental::set_error_t(Err)>{}; + } }; - template - using invoke_function_transformation = - invoke_function_transformation_helper::type; - - template - using default_set_error = - hpx::execution::experimental::completion_signatures< - hpx::execution::experimental::set_error_t(Err)>; - - using no_set_stopped_signature = - hpx::execution::experimental::completion_signatures<>; - // clang-format off - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - transform_mpi_sender const&, Env const&) - -> hpx::execution::experimental::transform_completion_signatures_of< - Sender, Env, - hpx::execution::experimental::completion_signatures< - hpx::execution::experimental::set_error_t(std::exception_ptr) - >, - invoke_function_transformation, - default_set_error, - no_set_stopped_signature - >; + template + static consteval auto get_completion_signatures() noexcept + -> decltype(hpx::execution::experimental::transform_completion_signatures( + hpx::execution::experimental::completion_signatures_of_t< + Sender, Env>{}, + invoke_function_transformation_fn{}, + default_set_error_fn{}, + hpx::execution::experimental::ignore_completion{})) + { + return hpx::execution::experimental::transform_completion_signatures( + hpx::execution::experimental::completion_signatures_of_t< + Sender, Env>{}, + invoke_function_transformation_fn{}, + default_set_error_fn{}, + hpx::execution::experimental::ignore_completion{}); + } // clang-format on template diff --git a/libs/core/errors/include/hpx/errors/exception_info.hpp b/libs/core/errors/include/hpx/errors/exception_info.hpp index 68b7e97e8721..a18b37196679 100644 --- a/libs/core/errors/include/hpx/errors/exception_info.hpp +++ b/libs/core/errors/include/hpx/errors/exception_info.hpp @@ -21,6 +21,8 @@ #undef exception_info #endif +#include + namespace hpx { /////////////////////////////////////////////////////////////////////////// @@ -252,3 +254,5 @@ namespace hpx { detail::access_exception(ec), HPX_FORWARD(F, f)); } } // namespace hpx + +#include diff --git a/libs/core/execution/CMakeLists.txt b/libs/core/execution/CMakeLists.txt index 9a3933c16d0c..56b8aa089f0f 100644 --- a/libs/core/execution/CMakeLists.txt +++ b/libs/core/execution/CMakeLists.txt @@ -14,8 +14,10 @@ set(execution_headers hpx/execution/algorithms/detail/partial_algorithm.hpp hpx/execution/algorithms/detail/predicates.hpp hpx/execution/algorithms/detail/single_result.hpp + hpx/execution/algorithms/detail/sync_wait_domain.hpp hpx/execution/algorithms/keep_future.hpp hpx/execution/algorithms/make_future.hpp + hpx/execution/algorithms/run_loop.hpp hpx/execution/algorithms/sync_wait.hpp hpx/execution/algorithms/when_all.hpp hpx/execution/algorithms/when_all_vector.hpp @@ -85,10 +87,11 @@ set(execution_headers hpx/execution/traits/vector_pack_load_store.hpp hpx/execution/traits/vector_pack_reduce.hpp hpx/execution/traits/vector_pack_type.hpp + hpx/synchronization/async_rw_mutex.hpp ) set(execution_sources execution_parameter_callbacks.cpp - polymorphic_executor.cpp + polymorphic_executor.cpp run_loop.cpp ) # cmake-format: off @@ -153,6 +156,7 @@ add_hpx_module( "hpx/execution/algorithms/detail/partial_algorithm.hpp" "hpx/execution/algorithms/detail/predicates.hpp" "hpx/execution/algorithms/detail/single_result.hpp" + "hpx/execution/algorithms/detail/sync_wait_domain.hpp" "hpx/execution/detail/async_launch_policy_dispatch.hpp" "hpx/execution/detail/execution_parameter_callbacks.hpp" "hpx/execution/detail/future_exec.hpp" diff --git a/libs/core/execution/include/hpx/execution/algorithms/as_sender.hpp b/libs/core/execution/include/hpx/execution/algorithms/as_sender.hpp index c4d56ebb0bba..cb21911acb8b 100644 --- a/libs/core/execution/include/hpx/execution/algorithms/as_sender.hpp +++ b/libs/core/execution/include/hpx/execution/algorithms/as_sender.hpp @@ -174,10 +174,12 @@ namespace hpx::execution::experimental { as_sender_sender(as_sender_sender const&) = delete; as_sender_sender& operator=(as_sender_sender const&) = delete; - template - friend auto tag_invoke( - get_completion_signatures_t, as_sender_sender const&, Env&&) -> - typename base_type::completion_signatures; + template + static consteval auto get_completion_signatures() noexcept -> + typename base_type::completion_signatures + { + return {}; + } template auto connect(Receiver&& receiver) && @@ -209,10 +211,12 @@ namespace hpx::execution::experimental { as_sender_sender(as_sender_sender const&) = default; as_sender_sender& operator=(as_sender_sender const&) = default; - template - friend auto tag_invoke( - get_completion_signatures_t, as_sender_sender const&, Env&&) -> - typename base_type::completion_signatures; + template + static consteval auto get_completion_signatures() noexcept -> + typename base_type::completion_signatures + { + return {}; + } template auto connect(Receiver&& receiver) && diff --git a/libs/core/execution/include/hpx/execution/algorithms/bulk.hpp b/libs/core/execution/include/hpx/execution/algorithms/bulk.hpp index 8aa3054c3a8b..526949664059 100644 --- a/libs/core/execution/include/hpx/execution/algorithms/bulk.hpp +++ b/libs/core/execution/include/hpx/execution/algorithms/bulk.hpp @@ -54,30 +54,47 @@ namespace hpx::execution::experimental { using disable_set_stopped = hpx::execution::experimental::completion_signatures<>; - template -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif - friend auto tag_invoke(get_completion_signatures_t, - bulk_sender const&, Env) noexcept -> hpx::execution:: - experimental::transform_completion_signatures< - hpx::execution::experimental::completion_signatures_of_t< - Sender, Env>, - hpx::execution::experimental::completion_signatures< + struct default_set_value_fn + { + template + consteval auto operator()() const noexcept + { + return hpx::execution::experimental::completion_signatures< + hpx::execution::experimental::set_value_t(Args...)>{}; + } + }; + + struct default_set_error_fn + { + template + consteval auto operator()() const noexcept + { + return hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_error_t( - std::exception_ptr)>, - default_set_value, default_set_error, disable_set_stopped>; -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic pop -#endif - - friend constexpr auto tag_invoke( - hpx::execution::experimental::get_env_t, - bulk_sender const& s) noexcept + std::decay_t)>{}; + } + }; + + template + static consteval auto get_completion_signatures() noexcept + -> decltype(hpx::execution::experimental:: + transform_completion_signatures( + hpx::execution::experimental:: + completion_signatures_of_t{}, + default_set_value_fn{}, default_set_error_fn{}, + hpx::execution::experimental::ignore_completion{}, + hpx::execution::experimental::completion_signatures< + hpx::execution::experimental::set_error_t( + std::exception_ptr)>{})) { - return hpx::execution::experimental::get_env(s.sender); + return {}; } + + constexpr auto get_env() const noexcept + { + return hpx::execution::experimental::get_env(sender); + }; + template struct bulk_receiver { @@ -183,7 +200,7 @@ namespace hpx::execution::experimental { is_sender_v && experimental::detail::is_completion_scheduler_tag_invocable_v< hpx::execution::experimental::set_value_t, Sender, - bulk_t, Shape, F + bulk_t, Shape, F&& > )> // clang-format on diff --git a/libs/core/execution/include/hpx/execution/algorithms/detail/sync_wait_domain.hpp b/libs/core/execution/include/hpx/execution/algorithms/detail/sync_wait_domain.hpp new file mode 100644 index 000000000000..76b340d77cff --- /dev/null +++ b/libs/core/execution/include/hpx/execution/algorithms/detail/sync_wait_domain.hpp @@ -0,0 +1,402 @@ +// Copyright (c) 2026 The HPX Authors +// +// 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) + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// HPX-aware sync_wait domain. +// +// stdexec::sync_wait normally blocks using OS condition variables via an +// internal run_loop. When called on an HPX worker thread, this can deadlock if +// completion requires more work on the same HPX thread pool, especially with +// --hpx:threads=1. +// +// sync_wait_domain customizes apply_sender(sync_wait_t, sndr) to use HPX-aware +// waiting with hpx::spinlock + hpx::condition_variable_any. This suspends the +// HPX task cooperatively instead of OS-blocking the worker thread, allowing +// queued HPX work to continue running. +// +// Senders executing through HPX should expose this domain through +// get_completion_domain so sync_wait routes here. + +namespace hpx::execution::experimental::detail { + + HPX_CXX_CORE_EXPORT struct sync_wait_domain; + + // Marker for the set_stopped completion path. + HPX_CXX_CORE_EXPORT template + struct stopped_t + { + }; + + namespace sync_wait_detail { + + template + using attrs_completion_scheduler_t = std::decay_t< + decltype(hpx::execution::experimental::get_completion_scheduler< + hpx::execution::experimental::set_value_t>( + hpx::execution::experimental::get_env(std::declval())))>; + + template + struct can_attrs_completion_scheduler : std::false_type + { + }; + + template + struct can_attrs_completion_scheduler>>> + : std::integral_constant>>> + { + }; + } // namespace sync_wait_detail + + HPX_CXX_CORE_EXPORT template + inline constexpr bool sync_wait_can_query_attrs_completion_scheduler_v = + false; + + HPX_CXX_CORE_EXPORT template + inline constexpr bool sync_wait_can_query_attrs_completion_scheduler_v< + Sender, + std::enable_if_t< + sync_wait_detail::can_attrs_completion_scheduler::value>> = + true; + + HPX_CXX_CORE_EXPORT template + struct sync_wait_rcv_scheduler_impl + { + using type = std::decay_t< + decltype(std::declval() + .get_scheduler())>; + }; + + HPX_CXX_CORE_EXPORT template + struct sync_wait_rcv_scheduler_impl::value>> + { + using type = sync_wait_detail::attrs_completion_scheduler_t< + std::remove_cvref_t>; + }; + + HPX_CXX_CORE_EXPORT template + using sync_wait_rcv_scheduler_t = + typename sync_wait_rcv_scheduler_impl::type; + + // Receiver env: exposes a stdexec run_loop scheduler via the standard + // get_scheduler / get_delegation_scheduler queries so dependent senders + // (let_value, let_error, etc.) can compute their completion signatures + // against this environment. The run_loop is owned by shared_state and is + // NEVER actually run; it exists purely as a type carrier required by + // stdexec::sync_wait_t's constraint (sender_in). + // + // Modern P2300: Uses query() functions instead of old tag_invoke. + // + // IMPORTANT: We intentionally DO NOT provide a stop token. + // + // If we return never_stop_token, stdexec assumes the operation can never be + // stopped, so it removes set_stopped from the completion signatures. + // + // But our code may still call set_stopped(). That creates type mismatches + // and breaks things. + // + // By not exposing a stop token, stdexec keeps set_stopped in the completion + // signatures, which matches our behavior. + HPX_CXX_CORE_EXPORT template + struct sync_wait_rcv_env + { + using scheduler_type = sync_wait_rcv_scheduler_t; + + scheduler_type sched_; + + constexpr explicit sync_wait_rcv_env(scheduler_type sched) noexcept + : sched_(HPX_MOVE(sched)) + { + } + + template + static sync_wait_rcv_env make( + S&& sndr, hpx::execution::experimental::run_loop& fallback_loop) + { + if constexpr (sync_wait_can_query_attrs_completion_scheduler_v< + std::remove_cvref_t>) + { + return sync_wait_rcv_env( + hpx::execution::experimental::get_completion_scheduler< + hpx::execution::experimental::set_value_t>( + hpx::execution::experimental::get_env(sndr))); + } + else + { + return sync_wait_rcv_env(fallback_loop.get_scheduler()); + } + } + + [[nodiscard]] + constexpr auto query( + hpx::execution::experimental::get_scheduler_t) const noexcept + { + return sched_; + } + + [[nodiscard]] + constexpr auto query( + hpx::execution::experimental::get_start_scheduler_t) const noexcept + { + return sched_; + } + + [[nodiscard]] + constexpr auto query( + hpx::execution::experimental::get_delegation_scheduler_t) + const noexcept + { + return sched_; + } + + template + [[nodiscard]] + static constexpr auto query( + hpx::execution::experimental::get_completion_domain_t) noexcept + -> sync_wait_domain; + }; + + // Compute the single-value tuple type for a sender against the receiver + // env, using only public stdexec APIs. + HPX_CXX_CORE_EXPORT template + using decayed_std_tuple = std::tuple...>; + + HPX_CXX_CORE_EXPORT template + struct first_alternative; + + HPX_CXX_CORE_EXPORT template + struct first_alternative> + { + using type = T; + }; + + HPX_CXX_CORE_EXPORT template + using value_tuple_for_t = + first_alternative, decayed_std_tuple, std::variant>>::type; + + HPX_CXX_CORE_EXPORT template + using into_variant_sender_t = + std::remove_cvref_t()))>; + + HPX_CXX_CORE_EXPORT template + using value_variant_for_t = std::tuple_element_t<0, + value_tuple_for_t>>; + + HPX_CXX_CORE_EXPORT template + struct shared_state + { + hpx::spinlock mtx; + hpx::condition_variable_any cv; + hpx::execution::experimental::run_loop loop; + bool done = false; + std::variant> + result; + + template + void notify_value(Vs&&... vs) noexcept + { + try + { + { + std::unique_lock l(mtx); + result.template emplace(HPX_FORWARD(Vs, vs)...); + done = true; + } + cv.notify_all(); + } + catch (...) + { + { + std::unique_lock l(mtx); + result.template emplace( + std::current_exception()); + done = true; + } + cv.notify_all(); + } + } + + template + void notify_error(E&& e) noexcept + { + { + std::unique_lock l(mtx); + using err_t = std::decay_t; + if constexpr (std::is_same_v) + { + result.template emplace( + HPX_FORWARD(E, e)); + } + else if constexpr (std::is_same_v) + { + result.template emplace( + std::make_exception_ptr(std::system_error(e))); + } + else + { + try + { + throw HPX_FORWARD(E, e); + } + catch (...) + { + result.template emplace( + std::current_exception()); + } + } + done = true; + } + cv.notify_all(); + } + + void notify_stopped() noexcept + { + { + std::unique_lock l(mtx); + result.template emplace>(); + done = true; + } + cv.notify_all(); + } + + // Wait HPX-aware: yields the calling HPX task while waiting, does not + // block the underlying OS thread. + std::optional wait_get_value() + { + { + std::unique_lock l(mtx); + cv.wait(l, [this] { return done; }); + } + + loop.finish(); + loop.run(); + + if (auto* v = std::get_if(&result)) + { + return std::optional(HPX_MOVE(*v)); + } + if (auto* ep = std::get_if(&result)) + { + auto e = HPX_MOVE(*ep); + std::rethrow_exception(HPX_MOVE(e)); + } + // set_stopped + return std::optional{}; + } + }; + + HPX_CXX_CORE_EXPORT template + struct receiver + { + using receiver_concept = hpx::execution::experimental::receiver_t; + + shared_state* state; + sync_wait_rcv_env env_; + + template + constexpr void set_value(Vs&&... vs) && noexcept + { + state->notify_value(HPX_FORWARD(Vs, vs)...); + } + + template + constexpr void set_error(E&& e) && noexcept + { + state->notify_error(HPX_FORWARD(E, e)); + } + + constexpr void set_stopped() && noexcept + { + state->notify_stopped(); + } + + [[nodiscard]] + constexpr sync_wait_rcv_env get_env() const noexcept + { + return env_; + } + }; + + // stdexec domain customizing only `apply_sender(sync_wait_t, ...)` to use + // HPX-aware cooperative waiting. + HPX_CXX_CORE_EXPORT struct sync_wait_domain + : hpx::execution::experimental::default_domain + { + // P2300/P2855 customization: stdexec::sync_wait dispatches here when + // the sender's completion domain is thread_pool_domain. We implement + // sync_wait using HPX synchronization primitives so the calling HPX + // task yields cooperatively rather than the OS thread being blocked, + // avoiding deadlock with --hpx:threads=1. + template + requires hpx::execution::experimental::sender_in> + auto apply_sender(hpx::execution::experimental::sync_wait_t, + Sender&& sndr) const -> std::optional> + { + using value_tuple_t = value_tuple_for_t; + + shared_state state; + auto env = sync_wait_rcv_env::make(sndr, state.loop); + + auto op_state = + hpx::execution::experimental::connect(HPX_FORWARD(Sender, sndr), + receiver{&state, HPX_MOVE(env)}); + hpx::execution::experimental::start(op_state); + return state.wait_get_value(); + } + + template + requires hpx::execution::experimental::sender_in< + into_variant_sender_t, + sync_wait_rcv_env>> + auto apply_sender( + hpx::execution::experimental::sync_wait_with_variant_t, + Sender&& sndr) const -> std::optional> + { + auto opt = apply_sender(hpx::execution::experimental::sync_wait_t{}, + hpx::execution::experimental::into_variant( + HPX_FORWARD(Sender, sndr))); + if (!opt) + { + return std::nullopt; + } + return std::make_optional(std::get<0>(std::move(*opt))); + } + }; + + template + template + constexpr auto sync_wait_rcv_env::query( + hpx::execution::experimental::get_completion_domain_t) noexcept + -> sync_wait_domain + { + return {}; + } +} // namespace hpx::execution::experimental::detail diff --git a/libs/core/execution/include/hpx/execution/algorithms/keep_future.hpp b/libs/core/execution/include/hpx/execution/algorithms/keep_future.hpp index 164b25868a56..3a939a878e70 100644 --- a/libs/core/execution/include/hpx/execution/algorithms/keep_future.hpp +++ b/libs/core/execution/include/hpx/execution/algorithms/keep_future.hpp @@ -95,10 +95,12 @@ namespace hpx::execution::experimental { keep_future_sender(keep_future_sender const&) = delete; keep_future_sender& operator=(keep_future_sender const&) = delete; - template - friend auto tag_invoke( - get_completion_signatures_t, keep_future_sender const&, Env&&) - -> typename base_type::completion_signatures; + template + static consteval auto get_completion_signatures() noexcept -> + typename base_type::completion_signatures + { + return {}; + } template operation_state connect( @@ -130,10 +132,12 @@ namespace hpx::execution::experimental { keep_future_sender(keep_future_sender const&) = default; keep_future_sender& operator=(keep_future_sender const&) = default; - template - friend auto tag_invoke( - get_completion_signatures_t, keep_future_sender const&, Env&&) - -> typename base_type::completion_signatures; + template + static consteval auto get_completion_signatures() noexcept -> + typename base_type::completion_signatures + { + return {}; + } template operation_state connect( diff --git a/libs/core/execution/include/hpx/execution/algorithms/make_future.hpp b/libs/core/execution/include/hpx/execution/algorithms/make_future.hpp index da41952550de..e5ccef16bf9f 100644 --- a/libs/core/execution/include/hpx/execution/algorithms/make_future.hpp +++ b/libs/core/execution/include/hpx/execution/algorithms/make_future.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -31,32 +32,26 @@ namespace hpx::execution::experimental { // Recover the parent `run_loop&` from a `run_loop::scheduler`. // - // P2300 deliberately does not provide a public API to obtain the - // owning `run_loop&` from one of its schedulers. The only way to do - // this against the current stdexec implementation is to read the - // private `__loop_` member exposed by the scheduler's environment. - // - // This helper is the single point in HPX that touches that internal - // detail. If upstream stdexec ever renames/removes `__loop_` (as - // happened with `stdexec::tags`), only this function needs to change. - // Post-cleanup follow-up of #7123. + // P2300 deliberately does not provide a public API to obtain the owning + // `run_loop&` from one of its schedulers. The only way to do this + // against the current stdexec implementation is to read the private + // `loop` member exposed by the scheduler's environment. // // The parameter is constrained to the concrete `run_loop::scheduler` // type (rather than a generic template) because the implementation - // depends on `.__loop_` being the specific stdexec env layout. - // `run_loop::scheduler::schedule()` is `noexcept`, so the - // unconditional `noexcept` here is sound. The function is not marked - // `constexpr` because the `stdexec::schedule` CPO wrapper is not - // declared `constexpr` (GCC strict mode rejects calling it from a - // constexpr context, even though the underlying member is constexpr). + // depends on `.loop` being the specific stdexec env layout. + // `run_loop::scheduler::schedule()` is `noexcept`, so the unconditional + // `noexcept` here is sound. The function is not marked `constexpr` + // because the `stdexec::schedule` CPO wrapper is not declared + // `constexpr` (GCC strict mode rejects calling it from a constexpr + // context, even though the underlying member is constexpr). inline hpx::execution::experimental::run_loop& get_run_loop_from_scheduler( decltype(std::declval() .get_scheduler()) const& sched) noexcept { return static_cast( - *hpx::execution::experimental::get_env(schedule(sched)) - .__loop_); + *hpx::execution::experimental::get_env(schedule(sched)).loop); } template @@ -110,7 +105,7 @@ namespace hpx::execution::experimental { data.reset(); } - void set_stopped() && noexcept + static void set_stopped() noexcept { std::terminate(); } @@ -161,8 +156,8 @@ namespace hpx::execution::experimental { using base_type = hpx::lcos::detail::future_data_allocator; using operation_state_type = std::decay_t; - using init_no_addref = typename base_type::init_no_addref; - using other_allocator = typename std::allocator_traits< + using init_no_addref = base_type::init_no_addref; + using other_allocator = std::allocator_traits< Allocator>::template rebind_alloc; operation_state_type op_state; @@ -194,8 +189,8 @@ namespace hpx::execution::experimental { using base_type = future_data; - using init_no_addref = typename base_type::init_no_addref; - using other_allocator = typename base_type::other_allocator; + using init_no_addref = base_type::init_no_addref; + using other_allocator = base_type::other_allocator; template future_data_with_run_loop(init_no_addref no_addref, @@ -240,8 +235,8 @@ namespace hpx::execution::experimental { using shared_state = future_data; - using init_no_addref = typename shared_state::init_no_addref; - using other_allocator = typename std::allocator_traits< + using init_no_addref = shared_state::init_no_addref; + using other_allocator = std::allocator_traits< allocator_type>::template rebind_alloc; using allocator_traits = std::allocator_traits; using unique_ptr = std::unique_ptr; - using init_no_addref = typename shared_state::init_no_addref; - using other_allocator = typename std::allocator_traits< + using init_no_addref = shared_state::init_no_addref; + using other_allocator = std::allocator_traits< allocator_type>::template rebind_alloc; using allocator_traits = std::allocator_traits; using unique_ptr = std::unique_ptr +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace hpx::execution::experimental { + + namespace detail { + + HPX_CXX_CORE_EXPORT struct sync_wait_domain; + + HPX_CXX_CORE_EXPORT struct run_loop_data; + + HPX_CORE_EXPORT void intrusive_ptr_add_ref(run_loop_data* p) noexcept; + HPX_CORE_EXPORT void intrusive_ptr_release(run_loop_data* p) noexcept; + + HPX_CXX_CORE_EXPORT struct run_loop_data + { + using mutex_type = hpx::spinlock; + + run_loop_data() noexcept + : count_(1) + { + } + + run_loop_data(run_loop_data const&) = delete; + run_loop_data(run_loop_data&&) = delete; + run_loop_data& operator=(run_loop_data const&) = delete; + run_loop_data& operator=(run_loop_data&&) = delete; + + ~run_loop_data() = default; + + mutable mutex_type mtx_; + + private: + friend HPX_CORE_EXPORT void intrusive_ptr_add_ref( + run_loop_data*) noexcept; + friend HPX_CORE_EXPORT void intrusive_ptr_release( + run_loop_data*) noexcept; + + hpx::util::atomic_count count_; + }; + } // namespace detail + + // A run_loop is an execution context on which work can be scheduled. It + // maintains a simple, thread-safe first-in-first-out queue of work. Its + // run() member function removes elements from the queue and executes them + // in a loop on whatever thread of execution calls run(). + // + // A run_loop instance has an associated count that corresponds to the + // number of work items that are in its queue. Additionally, a run_loop has + // an associated state that can be one of starting, running, or finishing. + // + // Concurrent invocations of the member functions of run_loop, other than + // run and its destructor, do not introduce data races. The member functions + // pop_front, push_back, and finish execute atomically. + // + // [Note: Implementations are encouraged to use an intrusive queue of + // operation states to hold the work units to make scheduling + // allocation-free. -- end note] + // + HPX_CXX_CORE_EXPORT class run_loop + { + struct run_loop_opstate_base + { + explicit run_loop_opstate_base(run_loop_opstate_base* tail) noexcept + : next(this) + , tail(tail) + { + } + + run_loop_opstate_base(run_loop_opstate_base* tail, + void (*execute)(run_loop_opstate_base*) noexcept) noexcept + : next(tail) + , execute_(execute) + { + } + + run_loop_opstate_base(run_loop_opstate_base const&) = delete; + run_loop_opstate_base(run_loop_opstate_base&&) = delete; + run_loop_opstate_base& operator=( + run_loop_opstate_base const&) = delete; + run_loop_opstate_base& operator=(run_loop_opstate_base&&) = delete; + + ~run_loop_opstate_base() = default; + + run_loop_opstate_base* next; + union + { + void (*execute_)(run_loop_opstate_base*) noexcept; + run_loop_opstate_base* tail; + }; + + void execute() noexcept + { + (*execute_)(this); + } + }; + + template + struct run_loop_opstate : run_loop_opstate_base + { + run_loop* loop; + HPX_NO_UNIQUE_ADDRESS std::decay_t receiver; + + template + run_loop_opstate(run_loop_opstate_base* tail, run_loop* loop, + Receiver_&& receiver) noexcept(noexcept(std:: + is_nothrow_constructible_v, + Receiver_>)) + : run_loop_opstate_base(tail) + , loop(loop) + , receiver(HPX_FORWARD(Receiver_, receiver)) + { + } + + static void execute(run_loop_opstate_base* p) noexcept + { + auto& receiver = static_cast(p)->receiver; + hpx::detail::try_catch_exception_ptr( + [&]() { + if (get_stop_token(get_env(receiver)).stop_requested()) + { + hpx::execution::experimental::set_stopped( + HPX_MOVE(receiver)); + } + else + { + hpx::execution::experimental::set_value( + HPX_MOVE(receiver)); + } + }, + [&](std::exception_ptr ep) { + hpx::execution::experimental::set_error( + HPX_MOVE(receiver), HPX_MOVE(ep)); + }); + } + + run_loop_opstate( + run_loop_opstate_base* next, run_loop* loop, Receiver r) + : run_loop_opstate_base(next, &execute) + , loop(loop) + , receiver(HPX_MOVE(r)) + { + } + + friend void tag_invoke(hpx::execution::experimental::start_t, + run_loop_opstate& os) noexcept + { + os.start(); + } + + void start() & noexcept; + }; + + struct env_t + { + [[nodiscard]] + auto query(get_completion_scheduler_t) const noexcept; + + [[nodiscard]] + auto query( + get_completion_scheduler_t) const noexcept; + + //[[nodiscard]] + //static auto query(get_completion_domain_t) noexcept; + + //[[nodiscard]] + //static auto query(get_completion_domain_t) noexcept; + + run_loop* loop; + }; + + public: + class run_loop_scheduler : public env_t + { + public: + struct run_loop_sender + { + using sender_concept = hpx::execution::experimental::sender_t; + + template + run_loop_opstate connect( + Receiver&& receiver) const noexcept + { + return run_loop_opstate( + &loop->head, loop, HPX_FORWARD(Receiver, receiver)); + } + + template + static consteval auto get_completion_signatures() noexcept + { + return completion_signatures{}; + } + + [[nodiscard]] constexpr env_t get_env() const noexcept + { + return env_t{loop}; + } + + private: + friend run_loop_scheduler; + + constexpr explicit run_loop_sender(run_loop* loop) noexcept + : loop(loop) + { + } + + run_loop* loop; + }; + + friend run_loop; + + public: + explicit run_loop_scheduler(run_loop* loop) noexcept + : env_t(loop) + { + } + + run_loop& get_run_loop() const noexcept + { + return *loop; + } + + run_loop_sender schedule() const noexcept + { + return run_loop_sender(loop); + } + + using env_t::query; + + [[nodiscard]] static constexpr auto query( + get_forward_progress_guarantee_t) noexcept + { + return forward_progress_guarantee::parallel; + } + + [[nodiscard]] friend constexpr bool operator==( + run_loop_scheduler const& lhs, + run_loop_scheduler const& rhs) noexcept + { + return lhs.loop == rhs.loop; + } + [[nodiscard]] friend constexpr bool operator!=( + run_loop_scheduler const& lhs, + run_loop_scheduler const& rhs) noexcept + { + return !(lhs == rhs); + } + }; + + private: + friend struct run_loop_scheduler::run_loop_sender; + + hpx::intrusive_ptr mtx; + hpx::lcos::local::detail::condition_variable cond_var; + + // MSVC and gcc don't properly handle the friend declaration above +#if defined(HPX_MSVC) || defined(HPX_GCC_VERSION) + public: +#endif + run_loop_opstate_base head; + + private: + bool stop = false; + + void push_back(run_loop_opstate_base* t) + { + auto const local_mtx = mtx; // keep alive + std::unique_lock l(local_mtx->mtx_); + + stop = false; + t->next = &head; + head.tail = head.tail->next = t; + cond_var.notify_one(HPX_MOVE(l)); + } + + run_loop_opstate_base* pop_front() + { + auto const local_mtx = mtx; // keep alive + std::unique_lock l(local_mtx->mtx_); + + while (head.next == &head && !stop) + { + cond_var.wait(l); + } + + if (head.tail == head.next) + { + head.tail = &head; + } + + // std::exchange(head.next, head.next->next); + auto const old_val = HPX_MOVE(head.next); + head.next = HPX_MOVE(head.next->next); + return old_val; + } + + public: + // [exec.run_loop.ctor] construct/copy/destroy + run_loop() noexcept + // NOLINTNEXTLINE(bugprone-unhandled-exception-at-new) + : mtx(new detail::run_loop_data(), false) + , head(&head) //-V546 + { + } + + run_loop(run_loop const&) = delete; + run_loop(run_loop&&) = delete; + run_loop& operator=(run_loop const&) = delete; + run_loop& operator=(run_loop&&) = delete; + + // If count is not 0 or if state is running, invokes terminate(). + // Otherwise, has no effects. + ~run_loop() + { + if (head.next != &head || !stop) + { + std::terminate(); + } + } + + // [exec.run_loop.members] Member functions: + run_loop_scheduler get_scheduler() + { + return run_loop_scheduler(this); + } + + void run() + { + // Precondition: state is starting. + //HPX_ASSERT(head.next != &head || !stop); + for (run_loop_opstate_base* t; (t = pop_front()) != &head; /**/) + { + t->execute(); + } + HPX_ASSERT(stop); // Postcondition: state is finishing. + } + + void finish() + { + auto const local_mtx = mtx; // keep alive + std::unique_lock l(local_mtx->mtx_); + + stop = true; + cond_var.notify_all(HPX_MOVE(l)); + } + }; + + /////////////////////////////////////////////////////////////////////////// + inline auto run_loop::env_t::query( + get_completion_scheduler_t) const noexcept + { + return run_loop_scheduler(loop); + } + + inline auto run_loop::env_t::query( + get_completion_scheduler_t) const noexcept + { + return query(get_completion_scheduler); + } + + //auto run_loop::env_t::query(get_completion_domain_t) noexcept + //{ + // return hpx::execution::experimental::detail::sync_wait_domain{}; + //} + + //inline auto run_loop::env_t::query( + // get_completion_domain_t) noexcept + //{ + // return query(get_completion_domain); + //} + + /////////////////////////////////////////////////////////////////////////// + HPX_CXX_CORE_EXPORT using run_loop_scheduler = run_loop::run_loop_scheduler; + + /////////////////////////////////////////////////////////////////////////// + template + inline void run_loop::run_loop_opstate::start() & noexcept + try + { + loop->push_back(this); + } + catch (...) + { + set_error(HPX_MOVE(receiver), std::current_exception()); + } +} // namespace hpx::execution::experimental diff --git a/libs/core/execution/include/hpx/execution/algorithms/sync_wait.hpp b/libs/core/execution/include/hpx/execution/algorithms/sync_wait.hpp index 7a081581a7f0..6581e18a8613 100644 --- a/libs/core/execution/include/hpx/execution/algorithms/sync_wait.hpp +++ b/libs/core/execution/include/hpx/execution/algorithms/sync_wait.hpp @@ -10,15 +10,62 @@ #include -#include +#include +#include +#include +#include + +#include namespace hpx::this_thread::experimental { - HPX_CXX_CORE_EXPORT using hpx::execution::experimental::sync_wait; - HPX_CXX_CORE_EXPORT using hpx::execution::experimental::sync_wait_t; + // HPX-aware sync_wait CPO. + // + // When called from an HPX worker thread, dispatch through + // `hpx::synchronization::detail::sync_wait_domain`, which uses cooperative + // HPX waiting (hpx::spinlock + hpx::condition_variable_any) instead of + // stdexec's default run_loop. The default run_loop OS-blocks the calling + // worker thread (futex_wait), which can deadlock at low worker-thread + // counts when the sender chain depends on other HPX work (e.g. an + // `hpx::async` task that has not yet been picked up, or a user-managed + // `stdexec::run_loop` driven by another HPX thread). + // + // When called from a non-HPX (OS) thread, fall back to stdexec's default + // sync_wait, which is correct in that context. + HPX_CXX_CORE_EXPORT inline constexpr struct sync_wait_t + { + template + constexpr auto operator()(Sender&& sndr) const + { + if (hpx::threads::get_self_ptr() != nullptr) + { + return hpx::execution::experimental::detail::sync_wait_domain{} + .apply_sender(hpx::execution::experimental::sync_wait_t{}, + HPX_FORWARD(Sender, sndr)); + } + return hpx::execution::experimental::sync_wait( + HPX_FORWARD(Sender, sndr)); + } + } sync_wait{}; - HPX_CXX_CORE_EXPORT using hpx::execution::experimental:: - sync_wait_with_variant; - HPX_CXX_CORE_EXPORT using hpx::execution::experimental:: - sync_wait_with_variant_t; + // HPX-aware sync_wait_with_variant. + // + // Prevents OS-level blocking when waiting on a sender from an HPX worker + // thread. Uses HPX-friendly waiting instead and also works for senders + HPX_CXX_CORE_EXPORT inline constexpr struct sync_wait_with_variant_t + { + template + constexpr auto operator()(Sender&& sndr) const + { + if (hpx::threads::get_self_ptr() != nullptr) + { + return hpx::execution::experimental::detail::sync_wait_domain{} + .apply_sender(hpx::execution::experimental:: + sync_wait_with_variant_t{}, + HPX_FORWARD(Sender, sndr)); + } + return hpx::execution::experimental::sync_wait_with_variant( + HPX_FORWARD(Sender, sndr)); + } + } sync_wait_with_variant{}; } // namespace hpx::this_thread::experimental diff --git a/libs/core/execution/include/hpx/execution/algorithms/when_all_vector.hpp b/libs/core/execution/include/hpx/execution/algorithms/when_all_vector.hpp index 7588707fcd67..4c1a2161a7b5 100644 --- a/libs/core/execution/include/hpx/execution/algorithms/when_all_vector.hpp +++ b/libs/core/execution/include/hpx/execution/algorithms/when_all_vector.hpp @@ -117,24 +117,43 @@ namespace hpx::when_all_vector_detail { hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_error_t(std::decay_t)>; - template -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - when_all_vector_sender_type const&, Env const&) noexcept - -> hpx::execution::experimental::transform_completion_signatures< - hpx::execution::experimental::completion_signatures_of_t, - hpx::execution::experimental::completion_signatures< + struct transformed_comp_sigs_identity_fn + { + template + consteval auto operator()() const noexcept + { + return hpx::execution::experimental::completion_signatures< + set_value_transform_to_vector>{}; + } + }; + + struct decay_set_error_fn + { + template + consteval auto operator()() const noexcept + { + return hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_error_t( - std::exception_ptr)>, - transformed_comp_sigs_identity, decay_set_error>; -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic pop -#endif + std::decay_t)>{}; + } + }; + + template + static consteval auto + get_completion_signatures() noexcept -> decltype(hpx::execution:: + experimental::transform_completion_signatures( + hpx::execution::experimental::completion_signatures_of_t< + Sender, Env>{}, + transformed_comp_sigs_identity_fn{}, decay_set_error_fn{}, + hpx::execution::experimental::keep_completion< + hpx::execution::experimental::set_stopped_t>{}, + hpx::execution::experimental::completion_signatures< + hpx::execution::experimental::set_error_t( + std::exception_ptr)>{})) + { + return {}; + } template struct operation_state @@ -151,45 +170,39 @@ namespace hpx::when_all_vector_detail { std::size_t const i; template - friend void tag_invoke( - hpx::execution::experimental::set_error_t, - when_all_vector_receiver&& r, Error&& error) noexcept + void set_error(Error&& error) && noexcept { - if (!r.op_state.set_stopped_error_called.exchange(true)) + if (!op_state.set_stopped_error_called.exchange(true)) { - r.op_state.stop_source_.request_stop(); + op_state.stop_source_.request_stop(); try { - r.op_state.error = HPX_FORWARD(Error, error); + op_state.error = HPX_FORWARD(Error, error); } catch (...) { // NOLINTNEXTLINE(bugprone-throw-keyword-missing) - r.op_state.error = std::current_exception(); + op_state.error = std::current_exception(); } } - r.op_state.finish(); + op_state.finish(); } - friend void tag_invoke( - hpx::execution::experimental::set_stopped_t, - when_all_vector_receiver&& r) noexcept + void set_stopped() && noexcept { // request stop only if we're not in error state - if (!r.op_state.set_stopped_error_called.exchange(true)) + if (!op_state.set_stopped_error_called.exchange(true)) { - r.op_state.stop_source_.request_stop(); + op_state.stop_source_.request_stop(); } - r.op_state.finish(); - }; + op_state.finish(); + } template - friend void tag_invoke( - hpx::execution::experimental::set_value_t, - when_all_vector_receiver&& r, Ts&&... ts) noexcept + void set_value(Ts&&... ts) && noexcept { - if (!r.op_state.set_stopped_error_called) + if (!op_state.set_stopped_error_called) { try { @@ -199,48 +212,40 @@ namespace hpx::when_all_vector_detail { // senders that send nothing. if constexpr (sizeof...(Ts) == 1) { - r.op_state.ts[r.i].emplace( - HPX_FORWARD(Ts, ts)...); + op_state.ts[i].emplace(HPX_FORWARD(Ts, ts)...); } } catch (...) { - if (!r.op_state.set_stopped_error_called.exchange( + if (!op_state.set_stopped_error_called.exchange( true)) { // NOLINTNEXTLINE(bugprone-throw-keyword-missing) - r.op_state.error = std::current_exception(); + op_state.error = std::current_exception(); } } } - r.op_state.finish(); + op_state.finish(); } // clang-format off - // TODO: Make this a method - friend auto tag_invoke(hpx::execution::experimental::get_env_t, - when_all_vector_receiver const& r) - noexcept - -> hpx::execution::experimental::env< - hpx::execution::experimental::env_of_t, - hpx::execution::experimental::prop< - hpx::execution::experimental::get_stop_token_t, - hpx::experimental::in_place_stop_token>> + auto get_env() const noexcept { /* The new calling convention is: - * env(old_env, prop(tag, val))*/ + * make_env(old_env, prop(tag, val))*/ + // Due to the bug described in the get_env.cpp tests, // returning an env constructed directly with the // temporaries returned by the functions causes wrong // behaviour. auto e = hpx::execution::experimental::get_env( - r.op_state.receiver); + op_state.receiver); auto p = hpx::execution::experimental::prop( hpx::execution::experimental::get_stop_token, - r.op_state.stop_source_.get_token()); - return hpx::execution::experimental::env( + op_state.stop_source_.get_token()); + return hpx::execution::experimental::make_env( std::move(e), std::move(p)); } // clang-format on @@ -393,41 +398,40 @@ namespace hpx::when_all_vector_detail { } } - friend void tag_invoke(hpx::execution::experimental::start_t, - operation_state& os) noexcept + void start() & noexcept { // register stop callback - os.on_stop_.emplace( + on_stop_.emplace( hpx::execution::experimental::get_stop_token( - hpx::execution::experimental::get_env(os.receiver)), - on_stop_requested{os.stop_source_}); + hpx::execution::experimental::get_env(receiver)), + on_stop_requested{stop_source_}); // If a stop has already been requested. Don't bother starting // the child operations. - if (os.stop_source_.stop_requested()) + if (stop_source_.stop_requested()) { hpx::execution::experimental::set_stopped( - HPX_FORWARD(Receiver, os.receiver)); + HPX_FORWARD(Receiver, receiver)); return; } // If there are no predecessors we can signal the // continuation as soon as start is called. - if (os.num_predecessors == 0) + if (num_predecessors == 0) { // If the predecessor sender type sends nothing, we also // send nothing to the continuation. if constexpr (is_void_value_type) { hpx::execution::experimental::set_value( - HPX_MOVE(os.receiver)); + HPX_MOVE(receiver)); } // If the predecessor sender type sends something we // send an empty vector of that type to the continuation. else { hpx::execution::experimental::set_value( - HPX_MOVE(os.receiver), + HPX_MOVE(receiver), std::vector{}); } } @@ -435,14 +439,14 @@ namespace hpx::when_all_vector_detail { // the predecessors to signal completion. else { - for (std::size_t i = 0; i < os.num_predecessors; ++i) + for (std::size_t i = 0; i < num_predecessors; ++i) { #if defined(HPX_CLANG_VERSION) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif hpx::execution::experimental::start( - os.op_states.get()[i].value()); + op_states.get()[i].value()); #if defined(HPX_CLANG_VERSION) #pragma clang diagnostic pop #endif diff --git a/libs/core/synchronization/include/hpx/synchronization/async_rw_mutex.hpp b/libs/core/execution/include/hpx/synchronization/async_rw_mutex.hpp similarity index 89% rename from libs/core/synchronization/include/hpx/synchronization/async_rw_mutex.hpp rename to libs/core/execution/include/hpx/synchronization/async_rw_mutex.hpp index b52c1d601a03..1a714a848a91 100644 --- a/libs/core/synchronization/include/hpx/synchronization/async_rw_mutex.hpp +++ b/libs/core/execution/include/hpx/synchronization/async_rw_mutex.hpp @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -439,14 +440,38 @@ namespace hpx::experimental { AccessType>; using sender_concept = hpx::execution::experimental::sender_t; - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - sender const&, Env const&) + template + static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_value_t(access_type), hpx::execution::experimental::set_error_t( - std::exception_ptr)>; + std::exception_ptr)> + { + return {}; + } + + // Sender environment that advertises the HPX-aware sync_wait + // domain via get_completion_domain. Without this, + // stdexec::sync_wait routes through default_domain, whose + // run_loop OS-blocks the calling thread. Because async_rw_mutex + // continuations are dispatched on HPX worker threads, OS-blocking + // the caller deadlocks at low worker counts (--hpx:threads=1). + struct env_t + { + template + static constexpr auto query( + hpx::execution::experimental::get_completion_domain_t< + CPO>) noexcept + -> hpx::execution::experimental::detail::sync_wait_domain + { + return {}; + } + }; + + static constexpr env_t get_env() noexcept + { + return {}; + } template struct operation_state @@ -469,16 +494,14 @@ namespace hpx::experimental { operation_state(operation_state const&) = delete; operation_state& operator=(operation_state const&) = delete; - friend void tag_invoke(hpx::execution::experimental::start_t, - operation_state& os) noexcept + void start() & noexcept { - HPX_ASSERT_MSG(os.state, + HPX_ASSERT_MSG(state, "async_rw_lock::sender::operation_state state is " "empty, was the sender already started?"); auto continuation = - [r = HPX_MOVE(os.r)]( - shared_state_ptr_type state) mutable { + [r = HPX_MOVE(r)](shared_state_ptr_type state) mutable { try { hpx::execution::experimental::set_value( @@ -491,20 +514,20 @@ namespace hpx::experimental { } }; - if (os.prev_state) + if (prev_state) { - os.prev_state->add_continuation(HPX_MOVE(continuation)); + prev_state->add_continuation(HPX_MOVE(continuation)); // We release prev_state here to allow continuations to // run. The operation state may otherwise keep it alive // longer than needed. - os.prev_state.reset(); + prev_state.reset(); } else { // There is no previous state on the first access. We // can immediately trigger the continuation. - continuation(HPX_MOVE(os.state)); + continuation(HPX_MOVE(state)); } } }; @@ -635,14 +658,38 @@ namespace hpx::experimental { using sender_concept = hpx::execution::experimental::sender_t; - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - sender const&, Env) + template + static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_value_t(access_type), hpx::execution::experimental::set_error_t( - std::exception_ptr)>; + std::exception_ptr)> + { + return {}; + } + + // Sender environment that advertises the HPX-aware sync_wait + // domain via get_completion_domain. Without this, + // stdexec::sync_wait routes through default_domain, whose + // run_loop OS-blocks the calling thread. Because async_rw_mutex + // continuations are dispatched on HPX worker threads, OS-blocking + // the caller deadlocks at low worker counts (--hpx:threads=1). + struct env_t + { + template + static constexpr auto query( + hpx::execution::experimental::get_completion_domain_t< + CPO>) noexcept + -> hpx::execution::experimental::detail::sync_wait_domain + { + return {}; + } + }; + + constexpr env_t get_env() const noexcept + { + return {}; + } template struct operation_state @@ -665,16 +712,14 @@ namespace hpx::experimental { operation_state(operation_state const&) = delete; operation_state& operator=(operation_state const&) = delete; - friend void tag_invoke(hpx::execution::experimental::start_t, - operation_state& os) noexcept + void start() & noexcept { - HPX_ASSERT_MSG(os.state, + HPX_ASSERT_MSG(state, "async_rw_lock::sender::operation_state state is " "empty, was the sender already started?"); auto continuation = - [r = HPX_MOVE(os.r)]( - shared_state_ptr_type state) mutable { + [r = HPX_MOVE(r)](shared_state_ptr_type state) mutable { try { hpx::execution::experimental::set_value( @@ -687,19 +732,19 @@ namespace hpx::experimental { } }; - if (os.prev_state) + if (prev_state) { - os.prev_state->add_continuation(HPX_MOVE(continuation)); + prev_state->add_continuation(HPX_MOVE(continuation)); // We release prev_state here to allow continuations to // run. The operation state may otherwise keep it alive // longer than needed. - os.prev_state.reset(); + prev_state.reset(); } else { // There is no previous state on the first access. We // can immediately trigger the continuation. - continuation(HPX_MOVE(os.state)); + continuation(HPX_MOVE(state)); } } }; diff --git a/libs/core/execution/src/run_loop.cpp b/libs/core/execution/src/run_loop.cpp new file mode 100644 index 000000000000..95f0d1913bdb --- /dev/null +++ b/libs/core/execution/src/run_loop.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2023-2026 Hartmut Kaiser +// +// 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) + +#include + +#include + +/////////////////////////////////////////////////////////////////////////////// +namespace hpx::execution::experimental::detail { + + void intrusive_ptr_add_ref(run_loop_data* p) noexcept + { + p->count_.increment(); + } + + void intrusive_ptr_release(run_loop_data* p) noexcept + { + if (0 == p->count_.decrement()) + { + // The thread that decrements the reference count to zero must + // perform an acquire to ensure that it doesn't start destructing + // the object until all previous writes have drained. + std::atomic_thread_fence(std::memory_order_acquire); + + delete p; + } + } +} // namespace hpx::execution::experimental::detail diff --git a/libs/core/execution/tests/unit/CMakeLists.txt b/libs/core/execution/tests/unit/CMakeLists.txt index 819c7bfae08d..b3a2e8c4b4ec 100644 --- a/libs/core/execution/tests/unit/CMakeLists.txt +++ b/libs/core/execution/tests/unit/CMakeLists.txt @@ -7,8 +7,8 @@ set(tests algorithm_as_sender algorithm_bulk + algorithm_continues_on algorithm_ensure_started - algorithm_execute algorithm_just algorithm_just_error algorithm_just_stopped @@ -21,8 +21,6 @@ set(tests algorithm_sync_wait algorithm_sync_wait_with_variant algorithm_then - algorithm_transfer - algorithm_transfer_just algorithm_transfer_when_all algorithm_when_all algorithm_when_all_vector @@ -45,28 +43,50 @@ set(tests set(future_then_executor_PARAMETERS THREADS_PER_LOCALITY 4) -if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - foreach(test ${tests}) - set(sources ${test}.cpp) +foreach(test ${tests}) + set(sources ${test}.cpp) - set(${test}_PARAMETERS THREADS_PER_LOCALITY 4) + set(${test}_PARAMETERS THREADS_PER_LOCALITY 4) - source_group("Source Files" FILES ${sources}) + source_group("Source Files" FILES ${sources}) - set(folder_name "Tests/Unit/Modules/Core/Execution") + set(folder_name "Tests/Unit/Modules/Core/Execution") - # add example executable - add_hpx_executable( - ${test}_test INTERNAL_FLAGS - SOURCES ${sources} ${${test}_FLAGS} - EXCLUDE_FROM_ALL - HPX_PREFIX ${HPX_BUILD_PREFIX} - FOLDER ${folder_name} - ) + # add example executable + add_hpx_executable( + ${test}_test INTERNAL_FLAGS + SOURCES ${sources} ${${test}_FLAGS} + EXCLUDE_FROM_ALL + HPX_PREFIX ${HPX_BUILD_PREFIX} + FOLDER ${folder_name} + ) + + target_link_libraries(${test}_test PRIVATE hpx_execution_test_utilities) - target_link_libraries(${test}_test PRIVATE hpx_execution_test_utilities) + add_hpx_unit_test("modules.execution" ${test} ${${test}_PARAMETERS}) - add_hpx_unit_test("modules.execution" ${test} ${${test}_PARAMETERS}) +endforeach() +if(HPX_WITH_CXX_MODULES AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")) + # Clang (last tested version is v22) fails compiling the following tests when + # C++ module support is enabled. + set(failing_clang_tests + algorithm_bulk + algorithm_ensure_started + algorithm_just + algorithm_just_error + algorithm_just_stopped + algorithm_let_error + algorithm_let_stopped + algorithm_let_value + algorithm_run_loop + algorithm_split + algorithm_when_all + algorithm_transfer_when_all + ) + foreach(test ${failing_clang_tests}) + target_compile_definitions( + ${test}_test PRIVATE HPX_HAVE_FORCE_NO_CXX_MODULES + ) endforeach() endif() diff --git a/libs/core/execution/tests/unit/algorithm_as_sender.cpp b/libs/core/execution/tests/unit/algorithm_as_sender.cpp index cff9a777e4ec..bc53076af42e 100644 --- a/libs/core/execution/tests/unit/algorithm_as_sender.cpp +++ b/libs/core/execution/tests/unit/algorithm_as_sender.cpp @@ -122,7 +122,20 @@ int hpx_main() set_value_called = true; HPX_TEST_EQ(value, 42); }; - tt::sync_wait(std::move(s) | ex::then(f)); + // Use connect/start pattern instead of sync_wait to avoid stdexec sync primitives + // blocking on HPX futures + auto r = callback_receiver{f, set_value_called}; + auto os = ex::connect(std::move(s), std::move(r)); + ex::start(os); + // Wait for the async callback (future needs ~100ms to resolve) + auto const deadline = + std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!set_value_called) + { + if (std::chrono::steady_clock::now() > deadline) + break; + hpx::this_thread::yield(); + } HPX_TEST(set_value_called); } diff --git a/libs/core/execution/tests/unit/algorithm_transfer_just.cpp b/libs/core/execution/tests/unit/algorithm_continues_on.cpp similarity index 55% rename from libs/core/execution/tests/unit/algorithm_transfer_just.cpp rename to libs/core/execution/tests/unit/algorithm_continues_on.cpp index cba9c32735f9..de1076a97f37 100644 --- a/libs/core/execution/tests/unit/algorithm_transfer_just.cpp +++ b/libs/core/execution/tests/unit/algorithm_continues_on.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -23,16 +24,6 @@ namespace ex = hpx::execution::experimental; -// This overload is only used to check dispatching. It is not a useful -// implementation. -template -auto tag_invoke(ex::transfer_just_t, scheduler2 s, T&& t) -{ - s.tag_invoke_overload_called = true; - return ex::transfer_just( - std::move(static_cast(s)), std::forward(t)); -} - int main() { // Success path @@ -41,8 +32,9 @@ int main() std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer_just(example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}); + auto s = ex::continues_on(ex::just(), + example_scheduler{scheduler_schedule_called, + scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); @@ -65,10 +57,9 @@ int main() std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer_just( + auto s = ex::continues_on(ex::just(3), example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}, - 3); + scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); @@ -91,37 +82,10 @@ int main() std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; std::atomic tag_invoke_overload_called{false}; - int x = 3; - auto s = ex::transfer_just( + auto s = ex::continues_on( + ex::just(custom_type_non_default_constructible{42}), example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}, - x); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [](int x) { HPX_TEST_EQ(x, 3); }; - auto r = callback_receiver{f, set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(set_value_called); - HPX_TEST(!tag_invoke_overload_called); - HPX_TEST(scheduler_schedule_called); - HPX_TEST(!scheduler_execute_called); - } - - { - std::atomic set_value_called{false}; - std::atomic scheduler_schedule_called{false}; - std::atomic scheduler_execute_called{false}; - std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer_just( - example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}, - custom_type_non_default_constructible{42}); + scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); @@ -145,40 +109,12 @@ int main() std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; std::atomic tag_invoke_overload_called{false}; - custom_type_non_default_constructible x{42}; - auto s = ex::transfer_just( + auto s = ex::continues_on( + ex::just(custom_type_non_default_constructible_non_copyable{42}), example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}, - x); + scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); - check_value_types< - hpx::variant>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [](auto x) { HPX_TEST_EQ(x.x, 42); }; - auto r = callback_receiver{f, set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(set_value_called); - HPX_TEST(!tag_invoke_overload_called); - HPX_TEST(scheduler_schedule_called); - HPX_TEST(!scheduler_execute_called); - } - - { - std::atomic set_value_called{false}; - std::atomic scheduler_schedule_called{false}; - std::atomic scheduler_execute_called{false}; - std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer_just( - example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}, - custom_type_non_default_constructible_non_copyable{42}); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - check_value_types>>(s); check_error_types>(s); @@ -196,41 +132,12 @@ int main() { std::atomic set_value_called{false}; - std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; - std::atomic tag_invoke_overload_called{false}; - custom_type_non_default_constructible_non_copyable x{42}; - auto s = ex::transfer_just( - example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}, - std::move(x)); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [](auto x) { HPX_TEST_EQ(x.x, 42); }; - auto r = callback_receiver{f, set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(set_value_called); - HPX_TEST(!tag_invoke_overload_called); - HPX_TEST(scheduler_schedule_called); - HPX_TEST(!scheduler_execute_called); - } - - { - std::atomic set_value_called{false}; std::atomic scheduler_schedule_called{false}; - std::atomic scheduler_execute_called{false}; std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer_just( + auto s = ex::continues_on(ex::just(std::string("hello"), 3), example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}, - std::string("hello"), 3); + scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); @@ -247,21 +154,19 @@ int main() ex::start(os); HPX_TEST(set_value_called); HPX_TEST(!tag_invoke_overload_called); - HPX_TEST(scheduler_schedule_called); HPX_TEST(!scheduler_execute_called); + HPX_TEST(scheduler_schedule_called); } + // operator| overload { std::atomic set_value_called{false}; - std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; + std::atomic scheduler_schedule_called{false}; std::atomic tag_invoke_overload_called{false}; - std::string str{"hello"}; - int x = 3; - auto s = ex::transfer_just( - example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}, - str, x); + auto s = ex::just(std::string("hello"), 3) | + ex::continues_on(example_scheduler{scheduler_schedule_called, + scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); @@ -269,12 +174,12 @@ int main() check_error_types>(s); check_sends_stopped(s); - auto f = [](std::string str, int x) { - HPX_TEST_EQ(str, std::string("hello")); + auto f = [](std::string s, int x) { + HPX_TEST_EQ(s, std::string("hello")); HPX_TEST_EQ(x, 3); }; auto r = callback_receiver{f, set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); + auto os = ex::connect(std::move(s), r); ex::start(os); HPX_TEST(set_value_called); HPX_TEST(!tag_invoke_overload_called); @@ -282,57 +187,29 @@ int main() HPX_TEST(!scheduler_execute_called); } - // stdexec's transfer_just no longer dispatches through this custom - // overload; it uses the scheduler directly. + // Failure path { - std::atomic set_value_called{false}; - std::atomic scheduler_schedule_called{false}; - std::atomic scheduler_execute_called{false}; + std::atomic set_error_called{false}; std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer_just( - scheduler2{example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}}, - 3); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [](int x) { HPX_TEST_EQ(x, 3); }; - auto r = callback_receiver{f, set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(set_value_called); - HPX_TEST(!tag_invoke_overload_called); - HPX_TEST(scheduler_schedule_called); - HPX_TEST(!scheduler_execute_called); - } - - { - std::atomic set_value_called{false}; std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; - std::atomic tag_invoke_overload_called{false}; - int x = 3; - auto s = ex::transfer_just( - scheduler2{example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}}, - x); + auto s = ex::continues_on(error_sender{}, + example_scheduler{scheduler_schedule_called, + scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); - check_value_types>>(s); - check_error_types>(s); + check_value_types>>(s); + check_error_types>(s); check_sends_stopped(s); - auto f = [](int x) { HPX_TEST_EQ(x, 3); }; - auto r = callback_receiver{f, set_value_called}; + auto r = error_callback_receiver{ + check_exception_ptr{}, set_error_called}; auto os = ex::connect(std::move(s), std::move(r)); ex::start(os); - HPX_TEST(set_value_called); + HPX_TEST(set_error_called); HPX_TEST(!tag_invoke_overload_called); + // schedule is called anyways HPX_TEST(scheduler_schedule_called); HPX_TEST(!scheduler_execute_called); } diff --git a/libs/core/execution/tests/unit/algorithm_execute.cpp b/libs/core/execution/tests/unit/algorithm_execute.cpp deleted file mode 100644 index 6bd0e48dc005..000000000000 --- a/libs/core/execution/tests/unit/algorithm_execute.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2020 ETH Zurich -// Copyright (c) 2022 Hartmut Kaiser -// -// 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) - -#include -#include - -#include "algorithm_test_utils.hpp" - -#include -#include -#include - -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif - -namespace ex = hpx::execution::experimental; - -static std::size_t friend_tag_invoke_schedule_calls = 0; -static std::size_t tag_invoke_execute_calls = 0; - -template -struct execute_example_sender -{ - using is_sender = void; - using sender_concept = ex::sender_t; - - friend env_with_scheduler tag_invoke( - ex::get_env_t, execute_example_sender const&) noexcept - { - return {}; - } - - // clang-format off - template - friend auto tag_invoke(ex::get_completion_signatures_t, - execute_example_sender const&, - Env) -> ex::completion_signatures; - struct operation_state - { - friend void tag_invoke(ex::start_t, operation_state&) noexcept {}; - }; - // clang-format on - - template - friend operation_state tag_invoke( - ex::connect_t, execute_example_sender&&, R&&) noexcept - { - return {}; - } -}; - -struct scheduler_1 -{ - using my_sender = execute_example_sender; - - friend my_sender tag_invoke(ex::schedule_t, scheduler_1) - { - ++friend_tag_invoke_schedule_calls; - return {}; - } - - bool operator==(scheduler_1 const&) const noexcept - { - return true; - } - - bool operator!=(scheduler_1 const&) const noexcept - { - return false; - } -}; - -struct scheduler_2 -{ - using my_sender = execute_example_sender; - - bool operator==(scheduler_2 const&) const noexcept - { - return true; - } - - bool operator!=(scheduler_2 const&) const noexcept - { - return false; - } -}; -scheduler_2::my_sender tag_invoke(ex::schedule_t, scheduler_2) -{ - ++friend_tag_invoke_schedule_calls; - return {}; -} - -template -void tag_invoke(ex::execute_t, scheduler_2, F&&) -{ - ++tag_invoke_execute_calls; -} - -struct f_struct_1 -{ - // clang-format off - void operator()() {}; - // clang-format on -}; - -struct f_struct_2 -{ - // clang-format off - void operator()(int) {}; - // clang-format on -}; - -struct f_struct_3 -{ - // clang-format off - void operator()(int = 42) {}; - // clang-format on -}; - -void f_fun_1() {} - -void f_fun_2(int) {} - -int main() -{ - { - scheduler_1 s1; - ex::execute(s1, f_struct_1{}); - ex::execute(s1, f_struct_3{}); - ex::execute(s1, &f_fun_1); - HPX_TEST_EQ(friend_tag_invoke_schedule_calls, std::size_t(3)); - HPX_TEST_EQ(tag_invoke_execute_calls, std::size_t(0)); - } - - { - scheduler_2 s2; - ex::execute(s2, f_struct_1{}); - ex::execute(s2, f_struct_3{}); - ex::execute(s2, &f_fun_1); - HPX_TEST_EQ(friend_tag_invoke_schedule_calls, std::size_t(3)); - HPX_TEST_EQ(tag_invoke_execute_calls, std::size_t(3)); - } - - return hpx::util::report_errors(); -} - -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic pop -#endif diff --git a/libs/core/execution/tests/unit/algorithm_let_error.cpp b/libs/core/execution/tests/unit/algorithm_let_error.cpp index 90cddccb5c86..5a084059fd0e 100644 --- a/libs/core/execution/tests/unit/algorithm_let_error.cpp +++ b/libs/core/execution/tests/unit/algorithm_let_error.cpp @@ -19,15 +19,6 @@ namespace ex = hpx::execution::experimental; -// This overload is only used to check dispatching. It is not a useful -// implementation. -template -auto tag_invoke(ex::let_error_t, custom_sender_tag_invoke s, F&&) -{ - s.tag_invoke_overload_called = true; - return void_sender{}; -} - int main() { // "Success" path, i.e. let_error gets to handle the error @@ -193,21 +184,6 @@ int main() HPX_TEST(let_error_callback_called); } - // tag_invoke overload - { - std::atomic tag_invoke_overload_called{false}; - auto s = custom_sender_tag_invoke{tag_invoke_overload_called} | - ex::let_error([&](std::exception_ptr) { return ex::just(); }); - HPX_TEST(tag_invoke_overload_called); - - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - } - // "Failure" path, i.e. let_error has no error to handle { std::atomic set_value_called{false}; diff --git a/libs/core/execution/tests/unit/algorithm_let_stopped.cpp b/libs/core/execution/tests/unit/algorithm_let_stopped.cpp index cc610bd52679..b823c7a8d6c8 100644 --- a/libs/core/execution/tests/unit/algorithm_let_stopped.cpp +++ b/libs/core/execution/tests/unit/algorithm_let_stopped.cpp @@ -19,15 +19,6 @@ namespace ex = hpx::execution::experimental; -// This overload is only used to check dispatching. It is not a useful -// implementation. -template -auto tag_invoke(ex::let_stopped_t, custom_sender_tag_invoke s, F&&) -{ - s.tag_invoke_overload_called = true; - return void_sender{}; -} - int main() { // "Success" path, i.e. let_stopped gets to handle the error @@ -206,21 +197,6 @@ int main() HPX_TEST(let_stopped_callback_called); } - // tag_invoke overload - { - std::atomic tag_invoke_overload_called{false}; - auto s = custom_sender_tag_invoke{tag_invoke_overload_called} | - ex::let_stopped([&](std::exception_ptr) { return ex::just(); }); - HPX_TEST(tag_invoke_overload_called); - - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - } - // "Failure" path, i.e. let_stopped has no error to handle { std::atomic set_value_called{false}; diff --git a/libs/core/execution/tests/unit/algorithm_let_value.cpp b/libs/core/execution/tests/unit/algorithm_let_value.cpp index 8d6a71ef7e3c..5604fdd792d1 100644 --- a/libs/core/execution/tests/unit/algorithm_let_value.cpp +++ b/libs/core/execution/tests/unit/algorithm_let_value.cpp @@ -21,15 +21,6 @@ namespace ex = hpx::execution::experimental; -// This overload is only used to check dispatching. It is not a useful -// implementation. -template -auto tag_invoke(ex::let_value_t, custom_sender_tag_invoke s, F&&) -{ - s.tag_invoke_overload_called = true; - return void_sender{}; -} - int main() { // Success path @@ -185,21 +176,6 @@ int main() HPX_TEST(let_value_callback_called); } - // tag_invoke overload - { - std::atomic tag_invoke_overload_called{false}; - auto s = custom_sender_tag_invoke{tag_invoke_overload_called} | - ex::let_value([]() { return ex::just(); }); - HPX_TEST(tag_invoke_overload_called); - - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - } - // Failure path { std::atomic set_error_called{false}; diff --git a/libs/core/execution/tests/unit/algorithm_run_loop.cpp b/libs/core/execution/tests/unit/algorithm_run_loop.cpp index bd76b873d496..3cce33da6a24 100644 --- a/libs/core/execution/tests/unit/algorithm_run_loop.cpp +++ b/libs/core/execution/tests/unit/algorithm_run_loop.cpp @@ -31,7 +31,7 @@ #include // Clang V11 ICE's on this test -#if !defined(HPX_CLANG_VERSION) || (HPX_CLANG_VERSION / 10000) != 11 +#if !defined(HPX_CLANG_VERSION) || ((HPX_CLANG_VERSION / 10000) > 22) #include #include @@ -88,8 +88,10 @@ void test_execute() hpx::thread::id parent_id = hpx::this_thread::get_id(); ex::run_loop loop; - ex::execute(loop.get_scheduler(), - [parent_id]() { HPX_TEST_EQ(hpx::this_thread::get_id(), parent_id); }); + ex::start_detached( + ex::schedule(loop.get_scheduler()) | ex::then([parent_id]() { + HPX_TEST_EQ(hpx::this_thread::get_id(), parent_id); + })); loop.finish(); loop.run(); @@ -104,27 +106,25 @@ struct check_context_receiver bool& executed; template - friend void tag_invoke( - ex::set_error_t, check_context_receiver&&, E&&) noexcept + void set_error(E&&) && noexcept { HPX_TEST(false); } - friend void tag_invoke(ex::set_stopped_t, check_context_receiver&&) noexcept + void set_stopped() && noexcept { HPX_TEST(false); } template - friend void tag_invoke( - ex::set_value_t, check_context_receiver&& r, Ts&&...) noexcept + void set_value(Ts&&...) && noexcept { - HPX_TEST_EQ(r.parent_id, hpx::this_thread::get_id()); + HPX_TEST_EQ(parent_id, hpx::this_thread::get_id()); HPX_TEST_NEQ(hpx::thread::id(hpx::threads::invalid_thread_id), hpx::this_thread::get_id()); - r.executed = true; - r.loop.finish(); + executed = true; + loop.finish(); } }; @@ -303,7 +303,7 @@ void test_transfer_basic() auto work2 = ex::then(work1, [=, ¤t_id]() { HPX_TEST_EQ(current_id, hpx::this_thread::get_id()); }); - auto transfer1 = ex::transfer(work2, sched); + auto transfer1 = ex::continues_on(work2, sched); auto work3 = ex::then(transfer1, [=, ¤t_id]() { hpx::thread::id new_id = hpx::this_thread::get_id(); HPX_TEST_EQ(current_id, new_id); @@ -313,7 +313,7 @@ void test_transfer_basic() auto work4 = ex::then(work3, [=, ¤t_id]() { HPX_TEST_EQ(current_id, hpx::this_thread::get_id()); }); - auto transfer2 = ex::transfer(work4, sched); + auto transfer2 = ex::continues_on(work4, sched); auto work5 = ex::then(transfer2, [=, ¤t_id]() { hpx::thread::id new_id = hpx::this_thread::get_id(); HPX_TEST_EQ(current_id, new_id); @@ -345,7 +345,7 @@ void test_transfer_arguments() HPX_TEST_EQ(current_id, hpx::this_thread::get_id()); return x / 2.0; }); - auto transfer1 = ex::transfer(work2, sched); + auto transfer1 = ex::continues_on(work2, sched); auto work3 = ex::then(transfer1, [=, ¤t_id](double x) { hpx::thread::id new_id = hpx::this_thread::get_id(); HPX_TEST_EQ(current_id, new_id); @@ -357,7 +357,7 @@ void test_transfer_arguments() HPX_TEST_EQ(current_id, hpx::this_thread::get_id()); return "result: " + std::to_string(x); }); - auto transfer2 = ex::transfer(work4, sched); + auto transfer2 = ex::continues_on(work4, sched); auto work5 = ex::then(transfer2, [=, ¤t_id](std::string s) { hpx::thread::id new_id = hpx::this_thread::get_id(); HPX_TEST_EQ(current_id, new_id); @@ -382,7 +382,7 @@ void test_just_void() hpx::thread::id parent_id = t.get_id(); auto begin = ex::just(); - auto transfer1 = ex::transfer(begin, loop.get_scheduler()); + auto transfer1 = ex::continues_on(begin, loop.get_scheduler()); auto work1 = ex::then(transfer1, [parent_id]() { HPX_TEST_EQ(parent_id, hpx::this_thread::get_id()); }); @@ -398,7 +398,7 @@ void test_just_one_arg() hpx::thread::id parent_id = t.get_id(); auto begin = ex::just(3); - auto transfer1 = ex::transfer(begin, loop.get_scheduler()); + auto transfer1 = ex::continues_on(begin, loop.get_scheduler()); auto work1 = ex::then(transfer1, [parent_id](int x) { HPX_TEST_EQ(parent_id, hpx::this_thread::get_id()); HPX_TEST_EQ(x, 3); @@ -416,7 +416,7 @@ void test_just_two_args() hpx::thread::id parent_id = t.get_id(); auto begin = ex::just(3, std::string("hello")); - auto transfer1 = ex::transfer(begin, loop.get_scheduler()); + auto transfer1 = ex::continues_on(begin, loop.get_scheduler()); auto work1 = ex::then(transfer1, [parent_id](int x, std::string y) { HPX_TEST_EQ(parent_id, hpx::this_thread::get_id()); HPX_TEST_EQ(x, 3); @@ -428,59 +428,6 @@ void test_just_two_args() t.join(); } -void test_transfer_just_void() -{ - ex::run_loop loop; - hpx::thread t = hpx::thread([&] { loop.run(); }); - hpx::thread::id parent_id = t.get_id(); - [[maybe_unused]] auto sched = loop.get_scheduler(); - - auto begin = ex::transfer_just(sched); - auto work1 = ex::then(begin, - [parent_id]() { HPX_TEST_EQ(parent_id, hpx::this_thread::get_id()); }); - - tt::sync_wait(work1); - loop.finish(); - t.join(); -} - -void test_transfer_just_one_arg() -{ - ex::run_loop loop; - hpx::thread t = hpx::thread([&] { loop.run(); }); - hpx::thread::id parent_id = t.get_id(); - [[maybe_unused]] auto sched = loop.get_scheduler(); - - auto begin = ex::transfer_just(sched, 3); - auto work1 = ex::then(begin, [parent_id](int x) { - HPX_TEST_EQ(parent_id, hpx::this_thread::get_id()); - HPX_TEST_EQ(x, 3); - }); - - tt::sync_wait(work1); - loop.finish(); - t.join(); -} - -void test_transfer_just_two_args() -{ - ex::run_loop loop; - hpx::thread t = hpx::thread([&] { loop.run(); }); - hpx::thread::id parent_id = t.get_id(); - [[maybe_unused]] auto sched = loop.get_scheduler(); - - auto begin = ex::transfer_just(sched, 3, std::string("hello")); - auto work1 = ex::then(begin, [parent_id](int x, std::string y) { - HPX_TEST_EQ(parent_id, hpx::this_thread::get_id()); - HPX_TEST_EQ(x, 3); - HPX_TEST_EQ(y, std::string("hello")); - }); - - tt::sync_wait(work1); - loop.finish(); - t.join(); -} - // Note: when_all does not propagate the completion scheduler, for this reason // any senders coming after it need to be explicitly provided with the required // scheduler again. @@ -610,7 +557,7 @@ void test_future_sender() ex::run_loop loop; [[maybe_unused]] auto sched = loop.get_scheduler(); - auto s = ex::transfer_just(sched, 3); + auto s = ex::just(3) | ex::continues_on(sched); auto f = ex::make_future(std::move(s)); HPX_TEST_EQ(f.get(), 3); } @@ -620,7 +567,7 @@ void test_future_sender() ex::run_loop loop; [[maybe_unused]] auto sched = loop.get_scheduler(); - auto f = ex::transfer_just(sched, 3) | ex::make_future(); + auto f = ex::just(3) | ex::continues_on(sched) | ex::make_future(); HPX_TEST_EQ(f.get(), 3); } @@ -650,9 +597,9 @@ void test_future_sender() ex::run_loop loop; [[maybe_unused]] auto sched = loop.get_scheduler(); - auto s1 = ex::transfer_just(sched, std::size_t(42)); - auto s2 = ex::transfer_just(sched, 3.14); - auto s3 = ex::transfer_just(sched, std::string("hello")); + auto s1 = ex::just(std::size_t(42)) | ex::continues_on(sched); + auto s2 = ex::just(3.14) | ex::continues_on(sched); + auto s3 = ex::just(std::string("hello")) | ex::continues_on(sched); auto f = ex::make_future(sched, ex::then(ex::when_all(std::move(s1), std::move(s2), std::move(s3)), [](std::size_t x, double, std::string z) { @@ -666,9 +613,18 @@ void test_future_sender() { ex::run_loop loop; [[maybe_unused]] auto sched = loop.get_scheduler(); - auto result = tt::sync_wait( - ex::as_sender(ex::make_future(ex::transfer_just(sched, 42)))); - HPX_TEST_EQ(hpx::get<0>(*result), 42); + + std::atomic called{false}; + auto s = ex::as_sender( + ex::make_future(ex::continues_on(ex::just(42), sched))); + auto r = callback_receiver{[&called](int value) { + called = true; + HPX_TEST_EQ(value, 42); + }, + called}; + auto os = ex::connect(std::move(s), std::move(r)); + ex::start(os); + HPX_TEST(called); } std::cout << "7\n"; @@ -681,9 +637,9 @@ void test_future_sender() return 42; }); - HPX_TEST_EQ( - ex::make_future(ex::transfer(ex::as_sender(std::move(f)), sched)) - .get(), + HPX_TEST_EQ(ex::make_future( + ex::continues_on(ex::as_sender(std::move(f)), sched)) + .get(), 42); } @@ -692,9 +648,9 @@ void test_future_sender() ex::run_loop loop; [[maybe_unused]] auto sched = loop.get_scheduler(); - auto s1 = ex::transfer_just(sched, std::size_t(42)); - auto s2 = ex::transfer_just(sched, 3.14); - auto s3 = ex::transfer_just(sched, std::string("hello")); + auto s1 = ex::continues_on(ex::just(std::size_t(42)), sched); + auto s2 = ex::continues_on(ex::just(3.14), sched); + auto s3 = ex::continues_on(ex::just(std::string("hello")), sched); auto f = ex::make_future(sched, ex::then(ex::when_all(std::move(s1), std::move(s2), std::move(s3)), [](std::size_t x, double, std::string z) { @@ -731,7 +687,7 @@ void test_ensure_started() auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto s = ex::transfer_just(sched, 42) | ex::ensure_started(); + auto s = ex::just(42) | ex::continues_on(sched) | ex::ensure_started(); auto result = tt::sync_wait(std::move(s)); HPX_TEST_EQ(hpx::get<0>(*result), 42); loop.finish(); @@ -743,8 +699,8 @@ void test_ensure_started() auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto s = ex::transfer_just(sched, 42) | ex::ensure_started() | - ex::transfer(sched); + auto s = ex::just(42) | ex::continues_on(sched) | ex::ensure_started() | + ex::continues_on(sched); auto result = tt::sync_wait(std::move(s)); HPX_TEST_EQ(hpx::get<0>(*result), 42); loop.finish(); @@ -755,8 +711,8 @@ void test_ensure_started() ex::run_loop loop; [[maybe_unused]] auto sched = loop.get_scheduler(); auto t = hpx::thread([&] { loop.run(); }); - auto s = - ex::transfer_just(sched, 42) | ex::ensure_started() | ex::split(); + auto s = ex::just(42) | ex::continues_on(sched) | ex::ensure_started() | + ex::split(); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); @@ -877,12 +833,12 @@ void test_ensure_started_when_all() } auto split_s = ex::split(std::move(s)); auto succ1 = - split_s | ex::transfer(sched) | ex::then([&](int const& x) { + split_s | ex::continues_on(sched) | ex::then([&](int const& x) { ++successor_task_calls; return x + 1; }); auto succ2 = - split_s | ex::transfer(sched) | ex::then([&](int const& x) { + split_s | ex::continues_on(sched) | ex::then([&](int const& x) { ++successor_task_calls; return x + 2; }); @@ -914,7 +870,7 @@ void test_split() auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto s = ex::transfer_just(sched, 42) | ex::split(); + auto s = ex::just(42) | ex::continues_on(sched) | ex::split(); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(std::move(s))), 42); loop.finish(); t.join(); @@ -925,8 +881,8 @@ void test_split() auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto s = - ex::transfer_just(sched, 42) | ex::split() | ex::transfer(sched); + auto s = ex::just(42) | ex::continues_on(sched) | ex::split() | + ex::continues_on(sched); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(std::move(s))), 42); loop.finish(); t.join(); @@ -937,7 +893,7 @@ void test_split() auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto s = ex::transfer_just(sched, 42) | ex::split(); + auto s = ex::just(42) | ex::continues_on(sched) | ex::split(); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); @@ -1032,12 +988,12 @@ void test_split_when_all() ++first_task_calls; return 3; }) | ex::split(); - auto succ1 = s | ex::transfer(sched) | ex::then([&](int const& x) { + auto succ1 = s | ex::continues_on(sched) | ex::then([&](int const& x) { HPX_TEST_EQ(first_task_calls, std::size_t(1)); ++successor_task_calls; return x + 1; }); - auto succ2 = s | ex::transfer(sched) | ex::then([&](int const& x) { + auto succ2 = s | ex::continues_on(sched) | ex::then([&](int const& x) { HPX_TEST_EQ(first_task_calls, std::size_t(1)); ++successor_task_calls; return x + 2; @@ -1071,8 +1027,10 @@ void test_let_value() ex::run_loop loop; auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto result = hpx::get<0>(*tt::sync_wait(ex::schedule(sched) | - ex::let_value([=]() { return ex::transfer_just(sched, 42); }))); + auto result = hpx::get<0>( + *tt::sync_wait(ex::schedule(sched) | ex::let_value([=]() { + return ex::just(42) | ex::continues_on(sched); + }))); HPX_TEST_EQ(result, 42); loop.finish(); t.join(); @@ -1082,8 +1040,10 @@ void test_let_value() ex::run_loop loop; auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto result = hpx::get<0>(*tt::sync_wait(ex::just() | - ex::let_value([=]() { return ex::transfer_just(sched, 42); }))); + auto result = + hpx::get<0>(*tt::sync_wait(ex::just() | ex::let_value([=]() { + return ex::just(42) | ex::continues_on(sched); + }))); HPX_TEST_EQ(result, 42); loop.finish(); t.join(); @@ -1094,8 +1054,9 @@ void test_let_value() ex::run_loop loop; auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto result = hpx::get<0>(*(tt::sync_wait(ex::transfer_just(sched, 43) | - ex::let_value([](int&) { return ex::just(42); })))); + auto result = + hpx::get<0>(*(tt::sync_wait(ex::just(43) | ex::continues_on(sched) | + ex::let_value([](int&) { return ex::just(42); })))); HPX_TEST_EQ(result, 42); loop.finish(); t.join(); @@ -1105,9 +1066,10 @@ void test_let_value() ex::run_loop loop; auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto result = hpx::get<0>(*(tt::sync_wait(ex::transfer_just(sched, 43) | - ex::let_value( - [=](int&) { return ex::transfer_just(sched, 42); })))); + auto result = hpx::get<0>(*(tt::sync_wait( + ex::just(43) | ex::continues_on(sched) | ex::let_value([=](int&) { + return ex::just(42) | ex::continues_on(sched); + })))); HPX_TEST_EQ(result, 42); loop.finish(); t.join(); @@ -1119,7 +1081,7 @@ void test_let_value() [[maybe_unused]] auto sched = loop.get_scheduler(); auto result = hpx::get<0>(*(tt::sync_wait(ex::just(43) | ex::let_value([=](int&) { - return ex::transfer_just(sched, 42); + return ex::just(42) | ex::continues_on(sched); })))); HPX_TEST_EQ(result, 42); loop.finish(); @@ -1132,7 +1094,7 @@ void test_let_value() auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); auto result = hpx::get<0>(*tt::sync_wait( - ex::transfer_just(sched, 43) | ex::let_value([](int& x) { + ex::just(43) | ex::continues_on(sched) | ex::let_value([](int& x) { return ex::just(42) | ex::then([&](int y) { return x + y; }); }))); HPX_TEST_EQ(result, 85); @@ -1145,8 +1107,8 @@ void test_let_value() auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); auto result = hpx::get<0>(*tt::sync_wait( - ex::transfer_just(sched, 43) | ex::let_value([=](int& x) { - return ex::transfer_just(sched, 42) | + ex::just(43) | ex::continues_on(sched) | ex::let_value([=](int& x) { + return ex::just(42) | ex::continues_on(sched) | ex::then([&](int y) { return x + y; }); }))); HPX_TEST_EQ(result, 85); @@ -1160,7 +1122,7 @@ void test_let_value() [[maybe_unused]] auto sched = loop.get_scheduler(); auto result = hpx::get<0>( *tt::sync_wait(ex::just(43) | ex::let_value([=](int& x) { - return ex::transfer_just(sched, 42) | + return ex::just(42) | ex::continues_on(sched) | ex::then([&](int y) { return x + y; }); }))); HPX_TEST_EQ(result, 85); @@ -1178,7 +1140,7 @@ void test_let_value() try { - tt::sync_wait(ex::transfer_just(sched, 43) | + tt::sync_wait(ex::just(43) | ex::continues_on(sched) | ex::then( [](int) -> int { throw std::runtime_error("error"); }) | ex::let_value([](int&) { @@ -1247,7 +1209,7 @@ void test_let_error() }) | ex::let_error([=, &called](std::exception_ptr& ep) { called = true; check_exception_ptr_message(ep, "error"); - return ex::transfer_just(sched); + return ex::just() | ex::continues_on(sched); })); HPX_TEST(called); loop.finish(); @@ -1265,7 +1227,7 @@ void test_let_error() }) | ex::let_error([=, &called](std::exception_ptr& ep) { called = true; check_exception_ptr_message(ep, "error"); - return ex::transfer_just(sched); + return ex::just() | ex::continues_on(sched); })); HPX_TEST(called); loop.finish(); @@ -1300,7 +1262,7 @@ void test_let_error() return 43; }) | ex::let_error([=](std::exception_ptr& ep) { check_exception_ptr_message(ep, "error"); - return ex::transfer_just(sched, 42); + return ex::just(42) | ex::continues_on(sched); }))); HPX_TEST_EQ(result, 42); loop.finish(); @@ -1316,7 +1278,7 @@ void test_let_error() return 43; }) | ex::let_error([=](std::exception_ptr& ep) { check_exception_ptr_message(ep, "error"); - return ex::transfer_just(sched, 42); + return ex::just(42) | ex::continues_on(sched); }))); HPX_TEST_EQ(result, 42); loop.finish(); @@ -1328,8 +1290,8 @@ void test_let_error() ex::run_loop loop; auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto result = hpx::get<0>(*tt::sync_wait(ex::transfer_just(sched, 42) | - ex::let_error([](std::exception_ptr) { + auto result = hpx::get<0>(*tt::sync_wait(ex::just(42) | + ex::continues_on(sched) | ex::let_error([](std::exception_ptr) { HPX_TEST(false); return ex::just(43); }))); @@ -1343,10 +1305,10 @@ void test_let_error() auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); - auto result = hpx::get<0>(*tt::sync_wait(ex::transfer_just(sched, 42) | - ex::let_error([=](std::exception_ptr) { + auto result = hpx::get<0>(*tt::sync_wait(ex::just(42) | + ex::continues_on(sched) | ex::let_error([=](std::exception_ptr) { HPX_TEST(false); - return ex::transfer_just(sched, 43); + return ex::just(43) | ex::continues_on(sched); }))); HPX_TEST_EQ(result, 42); loop.finish(); @@ -1360,7 +1322,7 @@ void test_let_error() auto result = hpx::get<0>(*tt::sync_wait( ex::just(42) | ex::let_error([=](std::exception_ptr) { HPX_TEST(false); - return ex::transfer_just(sched, 43); + return ex::just(43) | ex::continues_on(sched); }))); HPX_TEST_EQ(result, 42); loop.finish(); @@ -1426,37 +1388,49 @@ void test_keep_future_sender() // the future should be passed to then, not it's contained value { ex::run_loop loop; + auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); tt::sync_wait(ex::keep_future(hpx::make_ready_future()) | ex::then([](hpx::future&& f) { HPX_TEST(f.is_ready()); })); + loop.finish(); + t.join(); } { ex::run_loop loop; + auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); tt::sync_wait(ex::keep_future(hpx::make_ready_future().share()) | ex::then( [](hpx::shared_future&& f) { HPX_TEST(f.is_ready()); })); + loop.finish(); + t.join(); } { ex::run_loop loop; + auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); tt::sync_wait(ex::keep_future(hpx::make_ready_future(42)) | ex::then([](hpx::future&& f) { HPX_TEST(f.is_ready()); HPX_TEST_EQ(f.get(), 42); })); + loop.finish(); + t.join(); } { ex::run_loop loop; + auto t = hpx::thread([&] { loop.run(); }); [[maybe_unused]] auto sched = loop.get_scheduler(); tt::sync_wait(ex::keep_future(hpx::make_ready_future(42).share()) | ex::then([](hpx::shared_future&& f) { HPX_TEST(f.is_ready()); HPX_TEST_EQ(f.get(), 42); })); + loop.finish(); + t.join(); } { @@ -1493,8 +1467,8 @@ void test_keep_future_sender() } { - ex::run_loop loop; - [[maybe_unused]] auto sched = loop.get_scheduler(); + //ex::run_loop loop; + //[[maybe_unused]] auto sched = loop.get_scheduler(); std::atomic called{false}; auto f = hpx::async([&]() { @@ -1527,9 +1501,6 @@ void test_keep_future_sender() } { - ex::run_loop loop; - [[maybe_unused]] auto sched = loop.get_scheduler(); - std::atomic called{false}; auto f = hpx::async([&]() { called = true; @@ -1543,9 +1514,6 @@ void test_keep_future_sender() } { - ex::run_loop loop; - [[maybe_unused]] auto sched = loop.get_scheduler(); - std::atomic calls{0}; auto sf = hpx::async([&]() { ++calls; }).share(); tt::sync_wait(ex::keep_future(sf)); @@ -1567,9 +1535,6 @@ void test_keep_future_sender() } { - ex::run_loop loop; - [[maybe_unused]] auto sched = loop.get_scheduler(); - std::atomic calls{0}; auto sf = hpx::async([&]() { ++calls; @@ -1603,7 +1568,7 @@ void test_keep_future_sender() auto f = hpx::async([&]() { return 42; }); auto r = hpx::get<0>(*tt::sync_wait( - ex::keep_future(std::move(f)) | ex::transfer(sched))); + ex::keep_future(std::move(f)) | ex::continues_on(sched))); HPX_TEST(r.is_ready()); HPX_TEST_EQ(r.get(), 42); loop.finish(); @@ -1616,7 +1581,7 @@ void test_keep_future_sender() auto t = hpx::thread([&] { loop.run(); }); auto sf = hpx::async([&]() { return 42; }).share(); auto r = hpx::get<0>(*tt::sync_wait( - ex::keep_future(std::move(sf)) | ex::transfer(sched))); + ex::keep_future(std::move(sf)) | ex::continues_on(sched))); HPX_TEST(r.is_ready()); HPX_TEST_EQ(r.get(), 42); loop.finish(); @@ -1638,7 +1603,7 @@ void test_keep_future_sender() // noncopyable, and storing a reference is not acceptable since the // reference may outlive the value. auto r = hpx::get<0>(*tt::sync_wait( - ex::keep_future(std::move(sf)) | ex::transfer(sched))); + ex::keep_future(std::move(sf)) | ex::continues_on(sched))); HPX_TEST(r.is_ready()); HPX_TEST_EQ(r.get().x, 42); loop.finish(); @@ -1649,7 +1614,7 @@ void test_keep_future_sender() { ex::run_loop loop; auto t = hpx::thread([&] { loop.run(); }); - [[maybe_unused]] auto sched = loop.get_scheduler(); + //[[maybe_unused]] auto sched = loop.get_scheduler(); auto f = hpx::async([]() { return 42; }); auto sf = hpx::async([]() { return 3.14; }).share(); @@ -1678,7 +1643,7 @@ void test_keep_future_sender() HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait( ex::when_all(ex::keep_future(std::move(f)), ex::keep_future(sf)) | - ex::transfer(sched) | ex::then(fun))), + ex::continues_on(sched) | ex::then(fun))), 85); loop.finish(); @@ -1721,12 +1686,12 @@ void test_bulk() { std::vector v(n, -1); hpx::thread::id parent_id = t.get_id(); - auto v_out = hpx::get<0>( - *tt::sync_wait(ex::transfer_just(sched, std::move(v)) | - ex::bulk(n, [&parent_id](int i, std::vector& v) { - v[i] = i; - HPX_TEST_EQ(parent_id, hpx::this_thread::get_id()); - }))); + auto v_out = hpx::get<0>(*tt::sync_wait(ex::just(std::move(v)) | + ex::continues_on(sched) | + ex::bulk(n, [&parent_id](int i, std::vector& v) { + v[i] = i; + HPX_TEST_EQ(parent_id, hpx::this_thread::get_id()); + }))); for (int i = 0; i < n; ++i) { @@ -1737,9 +1702,6 @@ void test_bulk() t.join(); } - { - } - { ex::run_loop loop; auto t = hpx::thread([&] { loop.run(); }); @@ -1754,8 +1716,8 @@ void test_bulk() try { - tt::sync_wait( - ex::transfer_just(sched) | ex::bulk(n, [&v, i_fail](int i) { + tt::sync_wait(ex::just() | ex::continues_on(sched) | + ex::bulk(n, [&v, i_fail](int i) { if (i == i_fail) { throw std::runtime_error("error"); @@ -1812,7 +1774,6 @@ void test_completion_scheduler() { auto sender = ex::then(ex::schedule(sched), []() {}); - using hpx::functional::tag_invoke; auto completion_scheduler = ex::get_completion_scheduler(ex::get_env(sender)); static_assert( @@ -1822,7 +1783,7 @@ void test_completion_scheduler() } { - auto sender = ex::transfer_just(sched, 42); + auto sender = ex::just(42) | ex::continues_on(sched); auto completion_scheduler = ex::get_completion_scheduler(ex::get_env(sender)); static_assert( @@ -1842,8 +1803,8 @@ void test_completion_scheduler() } { - auto sender = ex::then( - ex::bulk(ex::transfer_just(sched, 42), 10, [](int, int) {}), + auto sender = ex::then(ex::bulk(ex::just(42) | ex::continues_on(sched), + 10, [](int, int) {}), [](int) {}); auto completion_scheduler = ex::get_completion_scheduler(ex::get_env(sender)); @@ -1854,9 +1815,9 @@ void test_completion_scheduler() } { - auto sender = - ex::bulk((ex::transfer_just(sched, 42) | ex::then([](int) {})), 10, - [](int) {}); + auto sender = ex::bulk( + (ex::just(42) | ex::continues_on(sched) | ex::then([](int) {})), 10, + [](int) {}); auto completion_scheduler = ex::get_completion_scheduler(ex::get_env(sender)); static_assert( @@ -1893,9 +1854,6 @@ int hpx_main() RUN_TEST(test_just_void); RUN_TEST(test_just_one_arg); RUN_TEST(test_just_two_args); - RUN_TEST(test_transfer_just_void); - RUN_TEST(test_transfer_just_one_arg); - RUN_TEST(test_transfer_just_two_args); RUN_TEST(test_when_all); RUN_TEST(test_future_sender); RUN_TEST(test_keep_future_sender); diff --git a/libs/core/execution/tests/unit/algorithm_split.cpp b/libs/core/execution/tests/unit/algorithm_split.cpp index a0c64b2f4ee7..bfe7f6799142 100644 --- a/libs/core/execution/tests/unit/algorithm_split.cpp +++ b/libs/core/execution/tests/unit/algorithm_split.cpp @@ -19,16 +19,6 @@ namespace ex = hpx::execution::experimental; -// This overload is only used to check dispatching. It is not a useful -// implementation. -template > -auto tag_invoke( - ex::split_t, custom_sender_tag_invoke s, Allocator const& = Allocator{}) -{ - s.tag_invoke_overload_called = true; - return void_sender{}; -} - int main() { // Success path @@ -151,29 +141,6 @@ int main() HPX_TEST(set_value_called); } - // tag_invoke overload - { - std::atomic receiver_set_value_called{false}; - std::atomic tag_invoke_overload_called{false}; - auto s = - custom_sender_tag_invoke{tag_invoke_overload_called} | ex::split(); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - // custom_sender_tag_invoke implements tag_invoke(split_t, ...) - // returning an instance of void_sender - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [] {}; - auto r = callback_receiver{f, receiver_set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(receiver_set_value_called); - HPX_TEST(tag_invoke_overload_called); - } - // Failure path { std::atomic set_error_called{false}; diff --git a/libs/core/execution/tests/unit/algorithm_start_detached.cpp b/libs/core/execution/tests/unit/algorithm_start_detached.cpp index 6ed68c898a8e..1eb271a424a2 100644 --- a/libs/core/execution/tests/unit/algorithm_start_detached.cpp +++ b/libs/core/execution/tests/unit/algorithm_start_detached.cpp @@ -19,13 +19,6 @@ namespace ex = hpx::execution::experimental; -// This overload is only used to check dispatching. It is not a useful -// implementation. -void tag_invoke(ex::start_detached_t, custom_sender2 s) -{ - s.tag_invoke_overload_called = true; -} - int main() { { @@ -80,17 +73,5 @@ int main() { } - // tag_invoke overload - { - std::atomic start_called{false}; - std::atomic connect_called{false}; - std::atomic tag_invoke_overload_called{false}; - ex::start_detached(custom_sender2{custom_sender{ - start_called, connect_called, tag_invoke_overload_called}}); - HPX_TEST(!start_called); - HPX_TEST(!connect_called); - HPX_TEST(tag_invoke_overload_called); - } - return hpx::util::report_errors(); } diff --git a/libs/core/execution/tests/unit/algorithm_sync_wait.cpp b/libs/core/execution/tests/unit/algorithm_sync_wait.cpp index 960b9e336f50..2974194c02fb 100644 --- a/libs/core/execution/tests/unit/algorithm_sync_wait.cpp +++ b/libs/core/execution/tests/unit/algorithm_sync_wait.cpp @@ -21,14 +21,6 @@ namespace ex = hpx::execution::experimental; namespace tt = hpx::this_thread::experimental; -// NOTE: This is not a conforming sync_wait implementation. It only exists to -// check that the tag_invoke overload is called. -std::optional> tag_invoke(tt::sync_wait_t, custom_sender2 s) -{ - s.tag_invoke_overload_called = true; - return {}; -} - // NOLINTBEGIN(bugprone-unchecked-optional-access) int hpx_main() { @@ -104,18 +96,6 @@ int hpx_main() HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(ex::just(3))), 3); } - // tag_invoke overload - { - std::atomic start_called{false}; - std::atomic connect_called{false}; - std::atomic tag_invoke_overload_called{false}; - tt::sync_wait(custom_sender2{custom_sender{ - start_called, connect_called, tag_invoke_overload_called}}); - HPX_TEST(!start_called); - HPX_TEST(!connect_called); - HPX_TEST(tag_invoke_overload_called); - } - // Failure path { bool exception_thrown = false; diff --git a/libs/core/execution/tests/unit/algorithm_sync_wait_with_variant.cpp b/libs/core/execution/tests/unit/algorithm_sync_wait_with_variant.cpp index 1bb99871667e..3d805d480016 100644 --- a/libs/core/execution/tests/unit/algorithm_sync_wait_with_variant.cpp +++ b/libs/core/execution/tests/unit/algorithm_sync_wait_with_variant.cpp @@ -22,15 +22,6 @@ namespace ex = hpx::execution::experimental; namespace tt = hpx::this_thread::experimental; -// NOTE: This is not a conforming sync_wait_with_variant implementation. -// It only exists to check that the tag_invoke overload is called. -std::optional>> tag_invoke( - tt::sync_wait_with_variant_t, custom_sender2 s) -{ - s.tag_invoke_overload_called = true; - return {}; -} - // NOLINTBEGIN(bugprone-unchecked-optional-access) int hpx_main() { @@ -256,18 +247,6 @@ int hpx_main() HPX_TEST(i == 3); } - // tag_invoke overload - { - std::atomic start_called{false}; - std::atomic connect_called{false}; - std::atomic tag_invoke_overload_called{false}; - tt::sync_wait_with_variant(custom_sender2{custom_sender{ - start_called, connect_called, tag_invoke_overload_called}}); - HPX_TEST(!start_called); - HPX_TEST(!connect_called); - HPX_TEST(tag_invoke_overload_called); - } - // Failure path { bool exception_thrown = false; diff --git a/libs/core/execution/tests/unit/algorithm_then.cpp b/libs/core/execution/tests/unit/algorithm_then.cpp index 6d9fdb909ab3..d509fb02266b 100644 --- a/libs/core/execution/tests/unit/algorithm_then.cpp +++ b/libs/core/execution/tests/unit/algorithm_then.cpp @@ -40,13 +40,6 @@ struct custom_transformer } }; -template -auto tag_invoke(ex::then_t, S&& s, custom_transformer t) -{ - t.tag_invoke_overload_called = true; - return ex::then(std::forward(s), [t = std::move(t)]() { t(); }); -} - /////////////////////////////////////////////////////////////////////////////// void test_execution_then_return_int() { @@ -333,30 +326,6 @@ int hpx_main() HPX_TEST(set_value_called); } - // tag_invoke overload - { - std::atomic receiver_set_value_called{false}; - std::atomic tag_invoke_overload_called{false}; - std::atomic custom_transformer_call_operator_called{false}; - auto s = ex::then(ex::just(), - custom_transformer{tag_invoke_overload_called, - custom_transformer_call_operator_called, false}); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [] {}; - auto r = callback_receiver{f, receiver_set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(receiver_set_value_called); - HPX_TEST(tag_invoke_overload_called); - HPX_TEST(custom_transformer_call_operator_called); - } - // Failure path { std::atomic set_error_called{false}; @@ -414,29 +383,6 @@ int hpx_main() HPX_TEST(set_error_called); } - { - std::atomic receiver_set_error_called{false}; - std::atomic tag_invoke_overload_called{false}; - std::atomic custom_transformer_call_operator_called{false}; - auto s = ex::then(ex::just(), - custom_transformer{tag_invoke_overload_called, - custom_transformer_call_operator_called, true}); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto r = error_callback_receiver{ - check_exception_ptr{}, receiver_set_error_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(receiver_set_error_called); - HPX_TEST(tag_invoke_overload_called); - HPX_TEST(custom_transformer_call_operator_called); - } - test_execution_then_return_int(); test_execution_then_return_void(); test_execution_then_return_int_shared(); diff --git a/libs/core/execution/tests/unit/algorithm_transfer.cpp b/libs/core/execution/tests/unit/algorithm_transfer.cpp index 963f671813bb..de1076a97f37 100644 --- a/libs/core/execution/tests/unit/algorithm_transfer.cpp +++ b/libs/core/execution/tests/unit/algorithm_transfer.cpp @@ -24,79 +24,6 @@ namespace ex = hpx::execution::experimental; -// schedule_from customization -struct scheduler_schedule_from - : example_scheduler_template -{ - using base = example_scheduler_template; - template - explicit scheduler_schedule_from(Args&&... ags) - : base(std::forward(ags)...) - { - } -}; - -template -auto tag_invoke(ex::schedule_from_t, scheduler_schedule_from sched, Sender&&) -{ - sched.tag_invoke_overload_called = true; - return scheduler_schedule_from::my_sender{std::move(sched)}; -} - -// transfer customization -struct scheduler_transfer : example_scheduler_template -{ - using base = example_scheduler_template; - - template - explicit scheduler_transfer(Args&&... args) - : base(std::forward(args)...) - { - } -}; - -template -decltype(auto) tag_invoke(ex::transfer_t, scheduler_transfer completion_sched, - Sender&& sender, example_scheduler&& sched) -{ - completion_sched.tag_invoke_overload_called = true; - return ex::schedule_from( - std::forward(sched), std::forward(sender)); -} - -struct sender_with_completion_scheduler : void_sender -{ - scheduler_transfer sched; - - explicit sender_with_completion_scheduler(scheduler_transfer sched) - : sched(std::move(sched)) - { - } - - struct my_env - { - scheduler_transfer const& sched; - - scheduler_transfer const& query( - ex::get_completion_scheduler_t) const noexcept - { - return sched; - } - }; - - my_env get_env() const noexcept - { - return {sched}; - } - - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - sender_with_completion_scheduler const&, Env) - -> hpx::execution::experimental::completion_signatures< - hpx::execution::experimental::set_value_t()>; -}; - int main() { // Success path @@ -105,14 +32,14 @@ int main() std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer(ex::just(), + auto s = ex::continues_on(ex::just(), example_scheduler{scheduler_schedule_called, scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); check_value_types>>(s); - check_error_types>(s); + check_error_types>(s); check_sends_stopped(s); auto f = [] {}; @@ -130,14 +57,14 @@ int main() std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer(ex::just(3), + auto s = ex::continues_on(ex::just(3), example_scheduler{scheduler_schedule_called, scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); check_value_types>>(s); - check_error_types>(s); + check_error_types>(s); check_sends_stopped(s); auto f = [](int x) { HPX_TEST_EQ(x, 3); }; @@ -155,16 +82,16 @@ int main() std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; std::atomic tag_invoke_overload_called{false}; - auto s = - ex::transfer(ex::just(custom_type_non_default_constructible{42}), - example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}); + auto s = ex::continues_on( + ex::just(custom_type_non_default_constructible{42}), + example_scheduler{scheduler_schedule_called, + scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); check_value_types< hpx::variant>>(s); - check_error_types>(s); + check_error_types>(s); check_sends_stopped(s); auto f = [](auto x) { HPX_TEST_EQ(x.x, 42); }; @@ -182,7 +109,7 @@ int main() std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer( + auto s = ex::continues_on( ex::just(custom_type_non_default_constructible_non_copyable{42}), example_scheduler{scheduler_schedule_called, scheduler_execute_called, tag_invoke_overload_called}); @@ -190,7 +117,7 @@ int main() static_assert(ex::is_sender_in_v); check_value_types>>(s); - check_error_types>(s); + check_error_types>(s); check_sends_stopped(s); auto f = [](auto x) { HPX_TEST_EQ(x.x, 42); }; @@ -208,14 +135,14 @@ int main() std::atomic scheduler_execute_called{false}; std::atomic scheduler_schedule_called{false}; std::atomic tag_invoke_overload_called{false}; - auto s = ex::transfer(ex::just(std::string("hello"), 3), + auto s = ex::continues_on(ex::just(std::string("hello"), 3), example_scheduler{scheduler_schedule_called, scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); check_value_types>>(s); - check_error_types>(s); + check_error_types>(s); check_sends_stopped(s); auto f = [](std::string s, int x) { @@ -238,13 +165,13 @@ int main() std::atomic scheduler_schedule_called{false}; std::atomic tag_invoke_overload_called{false}; auto s = ex::just(std::string("hello"), 3) | - ex::transfer(example_scheduler{scheduler_schedule_called, + ex::continues_on(example_scheduler{scheduler_schedule_called, scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); static_assert(ex::is_sender_in_v); check_value_types>>(s); - check_error_types>(s); + check_error_types>(s); check_sends_stopped(s); auto f = [](std::string s, int x) { @@ -260,121 +187,13 @@ int main() HPX_TEST(!scheduler_execute_called); } - // tag_invoke overloads - { - std::atomic set_value_called{false}; - std::atomic tag_invoke_overload_called{false}; - std::atomic scheduler_schedule_called{false}; - std::atomic scheduler_execute_called{false}; - auto s = ex::transfer(ex::just(), - scheduler_schedule_from{example_scheduler{scheduler_schedule_called, - scheduler_execute_called, tag_invoke_overload_called}}); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [] {}; - auto r = callback_receiver{f, set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(set_value_called); - HPX_TEST(tag_invoke_overload_called); - HPX_TEST(!scheduler_schedule_called); - HPX_TEST(!scheduler_execute_called); - } - - { - std::atomic set_value_called{false}; - std::atomic source_scheduler_tag_invoke_overload_called{false}; - std::atomic source_scheduler_schedule_called{false}; - std::atomic source_scheduler_execute_called{false}; - std::atomic destination_scheduler_tag_invoke_overload_called{ - false}; - std::atomic destination_scheduler_schedule_called{false}; - std::atomic destination_scheduler_execute_called{false}; - - scheduler_transfer source_scheduler{example_scheduler{ - source_scheduler_schedule_called, source_scheduler_execute_called, - source_scheduler_tag_invoke_overload_called}}; - example_scheduler destination_scheduler{ - example_scheduler{destination_scheduler_schedule_called, - destination_scheduler_execute_called, - destination_scheduler_tag_invoke_overload_called}}; - - auto s = ex::transfer( - sender_with_completion_scheduler{std::move(source_scheduler)}, - destination_scheduler); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [] {}; - auto r = callback_receiver{f, set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(set_value_called); - HPX_TEST(source_scheduler_tag_invoke_overload_called); - HPX_TEST(!source_scheduler_schedule_called); - HPX_TEST(!source_scheduler_execute_called); - HPX_TEST(!destination_scheduler_tag_invoke_overload_called); - HPX_TEST(destination_scheduler_schedule_called); - HPX_TEST(!destination_scheduler_execute_called); - } - - { - std::atomic set_value_called{false}; - std::atomic source_scheduler_tag_invoke_overload_called{false}; - std::atomic source_scheduler_schedule_called{false}; - std::atomic source_scheduler_execute_called{false}; - std::atomic destination_scheduler_tag_invoke_overload_called{ - false}; - std::atomic destination_scheduler_schedule_called{false}; - std::atomic destination_scheduler_execute_called{false}; - - scheduler_transfer source_scheduler{example_scheduler{ - source_scheduler_schedule_called, source_scheduler_execute_called, - source_scheduler_tag_invoke_overload_called}}; - scheduler_schedule_from destination_scheduler{ - example_scheduler{destination_scheduler_schedule_called, - destination_scheduler_execute_called, - destination_scheduler_tag_invoke_overload_called}}; - - auto s = ex::transfer( - sender_with_completion_scheduler{std::move(source_scheduler)}, - destination_scheduler); - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [] {}; - auto r = callback_receiver{f, set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(set_value_called); - HPX_TEST(source_scheduler_tag_invoke_overload_called); - HPX_TEST(!source_scheduler_schedule_called); - HPX_TEST(!source_scheduler_execute_called); - HPX_TEST(destination_scheduler_tag_invoke_overload_called); - HPX_TEST(!destination_scheduler_schedule_called); - HPX_TEST(!destination_scheduler_execute_called); - } - // Failure path { std::atomic set_error_called{false}; std::atomic tag_invoke_overload_called{false}; std::atomic scheduler_schedule_called{false}; std::atomic scheduler_execute_called{false}; - auto s = ex::transfer(error_sender{}, + auto s = ex::continues_on(error_sender{}, example_scheduler{scheduler_schedule_called, scheduler_execute_called, tag_invoke_overload_called}); static_assert(ex::is_sender_v); diff --git a/libs/core/execution/tests/unit/algorithm_transfer_when_all.cpp b/libs/core/execution/tests/unit/algorithm_transfer_when_all.cpp index 95811c73b83b..e295e78766a4 100644 --- a/libs/core/execution/tests/unit/algorithm_transfer_when_all.cpp +++ b/libs/core/execution/tests/unit/algorithm_transfer_when_all.cpp @@ -49,10 +49,6 @@ int main() static_assert(ex::is_sender_v, "transfer_when_all must return a sender"); - auto csch = - ex::get_completion_scheduler(ex::get_env(s)); - HPX_TEST(sched == csch); - auto f = [](int x) { HPX_TEST_EQ(x, 42); }; auto r = callback_receiver{f, set_value_called}; auto os = ex::connect(std::move(s), std::move(r)); @@ -76,10 +72,6 @@ int main() static_assert(ex::is_sender_v, "transfer_when_all must return a sender"); - auto csch = - ex::get_completion_scheduler(ex::get_env(s)); - HPX_TEST(sched == csch); - auto f = [](int x, std::string y, double z) { HPX_TEST_EQ(x, 42); HPX_TEST_EQ(y, std::string("hello")); @@ -107,10 +99,6 @@ int main() static_assert(ex::is_sender_v, "transfer_when_all must return a sender"); - auto csch = - ex::get_completion_scheduler(ex::get_env(s)); - HPX_TEST(sched == csch); - auto f = [](std::string y, double z) { HPX_TEST_EQ(y, std::string("hello")); HPX_TEST_EQ(z, 3.14); diff --git a/libs/core/execution/tests/unit/algorithm_when_all.cpp b/libs/core/execution/tests/unit/algorithm_when_all.cpp index e1aaf66c7415..b0c0790d8f68 100644 --- a/libs/core/execution/tests/unit/algorithm_when_all.cpp +++ b/libs/core/execution/tests/unit/algorithm_when_all.cpp @@ -35,15 +35,6 @@ namespace ex = hpx::execution::experimental; -// This overload is only used to check dispatching. It is not a useful -// implementation. -template -auto tag_invoke(ex::when_all_t, custom_sender_tag_invoke s, Ss&&... ss) -{ - s.tag_invoke_overload_called = true; - return ex::when_all(std::forward(ss)...); -} - int main() { // Success path @@ -281,26 +272,6 @@ int main() HPX_TEST(set_value_called); } - { - std::atomic receiver_set_value_called{false}; - std::atomic tag_invoke_overload_called{false}; - auto s = ex::when_all( - custom_sender_tag_invoke{tag_invoke_overload_called}, ex::just(42)); - - static_assert(ex::is_sender_v); - static_assert(ex::is_sender_in_v); - check_value_types>>(s); - check_error_types>(s); - check_sends_stopped(s); - - auto f = [](int x) { HPX_TEST_EQ(x, 42); }; - auto r = callback_receiver{f, receiver_set_value_called}; - auto os = ex::connect(std::move(s), std::move(r)); - ex::start(os); - HPX_TEST(receiver_set_value_called); - HPX_TEST(tag_invoke_overload_called); - } - // Failure path { std::atomic set_error_called{false}; diff --git a/libs/core/execution/tests/unit/algorithm_when_all_vector.cpp b/libs/core/execution/tests/unit/algorithm_when_all_vector.cpp index 7661caacb42b..c8d6a29fe1ee 100644 --- a/libs/core/execution/tests/unit/algorithm_when_all_vector.cpp +++ b/libs/core/execution/tests/unit/algorithm_when_all_vector.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2021 ETH Zurich -// Copyright (c) 2022 Hartmut Kaiser +// Copyright (c) 2022-2026 Hartmut Kaiser // // SPDX-License-Identifier: BSL-1.0 // Distributed under the Boost Software License, Version 1.0. (See accompanying @@ -7,9 +7,8 @@ #include -// Clang V11 ICE's on this test, Clang V8 reports a bogus constexpr problem -#if !defined(HPX_CLANG_VERSION) || \ - ((HPX_CLANG_VERSION / 10000) != 11 && (HPX_CLANG_VERSION / 10000) != 8) +// Clang up to V22 fails compiling this test +#if !defined(HPX_CLANG_VERSION) || ((HPX_CLANG_VERSION / 10000) > 22) #include #include @@ -373,7 +372,7 @@ int main() check_value_types< hpx::variant, std::vector>>>(s); check_error_types>(s); - check_sends_stopped(s); + check_sends_stopped(s); auto f = [](std::vector v1, std::vector v3) { HPX_TEST_EQ(v1.size(), std::size_t(3)); diff --git a/libs/core/execution/tests/unit/environment_queries.cpp b/libs/core/execution/tests/unit/environment_queries.cpp index 65fc25aa4c09..8b6f0d2b11e0 100644 --- a/libs/core/execution/tests/unit/environment_queries.cpp +++ b/libs/core/execution/tests/unit/environment_queries.cpp @@ -10,6 +10,7 @@ #include "algorithm_test_utils.hpp" +#include #include #include #include @@ -30,10 +31,20 @@ namespace mylib { using delegatee_sched = my_namespace::my_scheduler_template<0>; using delegatee_sched_env_t = ex::env>; + ex::prop>; struct allocator { + using value_type = std::byte; + value_type* allocate(std::size_t n) + { + return new value_type[n]; + } + void deallocate(value_type* p, std::size_t) + { + delete[] p; + } + bool operator==(allocator const&) const noexcept = default; }; using allocator_env_t = ex::env, "must return sched_env"); - auto delegatee_sched_env = ex::env(std::move(sched_env), - ex::prop(ex::get_delegatee_scheduler_t{}, delegatee_sched())); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-braces" + auto delegatee_sched_env = ex::env{sched_env, + ex::prop{ex::get_delegation_scheduler_t{}, delegatee_sched()}}; static_assert(std::is_same_v, "must return delegatee_sched_env"); - auto allocator_env = ex::env(std::move(delegatee_sched_env), - ex::prop(ex::get_allocator_t{}, allocator())); + auto allocator_env = ex::env{delegatee_sched_env, + ex::prop{ex::get_allocator_t{}, allocator()}}; static_assert( std::is_same_v, "must return allocator_env"); - auto stop_token_env = ex::env(std::move(allocator_env), - ex::prop(ex::get_stop_token_t{}, stop_token())); + auto stop_token_env = ex::env{ + allocator_env, ex::prop{ex::get_stop_token_t{}, stop_token()}}; static_assert( std::is_same_v, "must return stop_token_env"); +#pragma GCC diagnostic pop return stop_token_env; } @@ -133,7 +147,7 @@ int main() static_assert(std::is_same_v, "must return mylib::sched"); - auto delegatee_sched = ex::get_delegatee_scheduler(env); + auto delegatee_sched = ex::get_delegation_scheduler(env); static_assert( std::is_same_v, "must return mylib::delegatee_sched"); @@ -154,7 +168,7 @@ int main() } { mylib::receiver_1 rcv; - auto os = ex::connect(ex::get_delegatee_scheduler(), rcv); + auto os = ex::connect(ex::get_delegation_scheduler(), rcv); ex::start(os); HPX_TEST(set_value_delegatee_sched_called); } diff --git a/libs/core/execution/tests/unit/forward_progress_guarantee.cpp b/libs/core/execution/tests/unit/forward_progress_guarantee.cpp index 45606f0c6b23..6807302bd053 100644 --- a/libs/core/execution/tests/unit/forward_progress_guarantee.cpp +++ b/libs/core/execution/tests/unit/forward_progress_guarantee.cpp @@ -13,12 +13,12 @@ namespace mylib { - // CPO + // Using member query function (new stdexec API) struct inline_scheduler_0 { - constexpr friend HPX_FORCEINLINE auto tag_invoke( - hpx::execution::experimental::get_forward_progress_guarantee_t, - inline_scheduler_0 const&) noexcept + constexpr auto query( + hpx::execution::experimental::get_forward_progress_guarantee_t) + const noexcept { return hpx::execution::experimental::forward_progress_guarantee:: concurrent; @@ -26,19 +26,15 @@ namespace mylib { } scheduler{}; - // CPO + // Using member query function (new stdexec API) struct inline_scheduler_1 { - /// With the same user-defined tag_invoke overload, the user-defined - /// overload will now be used if it is a match even if it isn't an exact - /// match. - /// This is because tag_fallback will dispatch to tag_fallback_invoke only - /// if there are no matching tag_invoke overloads. - constexpr friend auto tag_invoke( - hpx::execution::experimental::get_forward_progress_guarantee_t, - inline_scheduler_1) noexcept + constexpr auto query( + hpx::execution::experimental::get_forward_progress_guarantee_t) + const noexcept { - return true; + return hpx::execution::experimental::forward_progress_guarantee:: + concurrent; } } scheduler_custom{}; @@ -55,7 +51,9 @@ int main() "forward_progress_guarantee should return concurrent"); static_assert(hpx::execution::experimental::get_forward_progress_guarantee( - scheduler_custom), + scheduler_custom) == + hpx::execution::experimental::forward_progress_guarantee:: + concurrent, "CPO should invoke user tag_invoke"); return hpx::util::report_errors(); diff --git a/libs/core/execution/tests/unit/rebind_executor_parameters.cpp b/libs/core/execution/tests/unit/rebind_executor_parameters.cpp index 2dc47c3c8a49..c2091be214fc 100644 --- a/libs/core/execution/tests/unit/rebind_executor_parameters.cpp +++ b/libs/core/execution/tests/unit/rebind_executor_parameters.cpp @@ -107,11 +107,12 @@ void replace_chunk_size() { std::atomic invoked_replaced(false); - auto params = - join_executor_parameters(experimental::static_chunk_size()); + auto params = join_executor_parameters( + hpx::execution::experimental::static_chunk_size()); auto rebound_params = rebind_executor_parameters( params, test_replaced_get_chunk_size(invoked_replaced)); - auto policy = create_rebound_policy(par, rebound_params); + auto policy = + create_rebound_policy(hpx::execution::par, rebound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -122,10 +123,12 @@ void replace_chunk_size() { std::atomic invoked_replaced(false); - auto params = join_executor_parameters(experimental::max_num_chunks()); + auto params = join_executor_parameters( + hpx::execution::experimental::max_num_chunks()); auto rebound_params = rebind_executor_parameters( params, test_replaced_get_chunk_size(invoked_replaced)); - auto policy = create_rebound_policy(par, rebound_params); + auto policy = + create_rebound_policy(hpx::execution::par, rebound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -137,9 +140,10 @@ void replace_chunk_size() auto params = join_executor_parameters( test_replaced_get_chunk_size(invoked_replaced)); - auto rebound_params = - rebind_executor_parameters(params, experimental::num_cores(4)); - auto policy = create_rebound_policy(par, rebound_params); + auto rebound_params = rebind_executor_parameters( + params, hpx::execution::experimental::num_cores(4)); + auto policy = + create_rebound_policy(hpx::execution::par, rebound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -151,10 +155,12 @@ void replace_chunk_size() std::atomic invoked_replaced(false); auto params = join_executor_parameters( - experimental::static_chunk_size(), experimental::num_cores(4)); + hpx::execution::experimental::static_chunk_size(), + hpx::execution::experimental::num_cores(4)); auto rebound_params = rebind_executor_parameters( params, test_replaced_get_chunk_size(invoked_replaced)); - auto policy = create_rebound_policy(par, rebound_params); + auto policy = + create_rebound_policy(hpx::execution::par, rebound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -169,7 +175,8 @@ void replace_chunk_size() test_replaced_get_chunk_size(invoked_inner_replaced)); auto rebound_params = rebind_executor_parameters( params, test_wrapping_replaced_get_chunk_size(invoked_replaced)); - auto policy = create_rebound_policy(par, rebound_params); + auto policy = + create_rebound_policy(hpx::execution::par, rebound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -245,7 +252,7 @@ void replace_measure_iteration() static_assert( extract_invokes_testing_function_v, "extract_invokes_testing_function_v"); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -256,13 +263,14 @@ void replace_measure_iteration() { std::atomic invoked_replaced(false); - auto params = join_executor_parameters(experimental::max_num_chunks()); + auto params = join_executor_parameters( + hpx::execution::experimental::max_num_chunks()); auto bound_params = rebind_executor_parameters( params, test_replaced_measure_iteration(invoked_replaced)); static_assert( extract_invokes_testing_function_v, "extract_invokes_testing_function_v"); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -274,12 +282,12 @@ void replace_measure_iteration() auto params = join_executor_parameters( test_replaced_measure_iteration(invoked_replaced)); - auto bound_params = - rebind_executor_parameters(params, experimental::num_cores(4)); + auto bound_params = rebind_executor_parameters( + params, hpx::execution::experimental::num_cores(4)); static_assert( extract_invokes_testing_function_v, "extract_invokes_testing_function_v"); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -290,14 +298,14 @@ void replace_measure_iteration() { std::atomic invoked_replaced(false); - auto params = join_executor_parameters( - base_measure_iteration(), experimental::num_cores(4)); + auto params = join_executor_parameters(base_measure_iteration(), + hpx::execution::experimental::num_cores(4)); auto bound_params = rebind_executor_parameters( params, test_replaced_measure_iteration(invoked_replaced)); static_assert( extract_invokes_testing_function_v, "extract_invokes_testing_function_v"); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -346,10 +354,11 @@ void replace_maximal_number_of_chunks() { std::atomic invoked_replaced(false); - auto params = join_executor_parameters(experimental::max_num_chunks()); + auto params = join_executor_parameters( + hpx::execution::experimental::max_num_chunks()); auto bound_params = rebind_executor_parameters( params, test_replaced_maximal_number_of_chunks(invoked_replaced)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -360,11 +369,11 @@ void replace_maximal_number_of_chunks() { std::atomic invoked_replaced(false); - auto params = - join_executor_parameters(experimental::static_chunk_size()); + auto params = join_executor_parameters( + hpx::execution::experimental::static_chunk_size()); auto bound_params = rebind_executor_parameters( params, test_replaced_maximal_number_of_chunks(invoked_replaced)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -377,9 +386,9 @@ void replace_maximal_number_of_chunks() auto params = join_executor_parameters( test_replaced_maximal_number_of_chunks(invoked_replaced)); - auto bound_params = - rebind_executor_parameters(params, experimental::num_cores(4)); - auto policy = create_rebound_policy(par, bound_params); + auto bound_params = rebind_executor_parameters( + params, hpx::execution::experimental::num_cores(4)); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -391,10 +400,11 @@ void replace_maximal_number_of_chunks() std::atomic invoked_replaced(false); auto params = join_executor_parameters( - experimental::max_num_chunks(), experimental::num_cores(4)); + hpx::execution::experimental::max_num_chunks(), + hpx::execution::experimental::num_cores(4)); auto bound_params = rebind_executor_parameters( params, test_replaced_maximal_number_of_chunks(invoked_replaced)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -498,7 +508,7 @@ void replace_execution_markers() auto bound_params = rebind_executor_parameters(params, test_replaced_execution_markers( invoked_begin, invoked_end, invoked_end_execution)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_begin); @@ -514,11 +524,12 @@ void replace_execution_markers() std::atomic invoked_end(false); std::atomic invoked_end_execution(false); - auto params = join_executor_parameters(experimental::max_num_chunks()); + auto params = join_executor_parameters( + hpx::execution::experimental::max_num_chunks()); auto bound_params = rebind_executor_parameters(params, test_replaced_execution_markers( invoked_begin, invoked_end, invoked_end_execution)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_begin); @@ -535,9 +546,9 @@ void replace_execution_markers() auto params = join_executor_parameters(test_replaced_execution_markers( invoked_begin, invoked_end, invoked_end_execution)); - auto bound_params = - rebind_executor_parameters(params, experimental::num_cores(4)); - auto policy = create_rebound_policy(par, bound_params); + auto bound_params = rebind_executor_parameters( + params, hpx::execution::experimental::num_cores(4)); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_begin); @@ -553,12 +564,12 @@ void replace_execution_markers() std::atomic invoked_end(false); std::atomic invoked_end_execution(false); - auto params = join_executor_parameters( - base_execution_markers(), experimental::num_cores(4)); + auto params = join_executor_parameters(base_execution_markers(), + hpx::execution::experimental::num_cores(4)); auto bound_params = rebind_executor_parameters(params, test_replaced_execution_markers( invoked_begin, invoked_end, invoked_end_execution)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_begin); @@ -629,7 +640,7 @@ void replace_processing_units_count() auto params = join_executor_parameters(base_processing_units_count()); auto bound_params = rebind_executor_parameters( params, test_replaced_processing_units_count(invoked_replaced)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -640,11 +651,11 @@ void replace_processing_units_count() { std::atomic invoked_replaced(false); - auto params = - join_executor_parameters(experimental::static_chunk_size()); + auto params = join_executor_parameters( + hpx::execution::experimental::static_chunk_size()); auto bound_params = rebind_executor_parameters( params, test_replaced_processing_units_count(invoked_replaced)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -658,8 +669,8 @@ void replace_processing_units_count() auto params = join_executor_parameters( test_replaced_processing_units_count(invoked_replaced)); auto bound_params = rebind_executor_parameters( - params, experimental::static_chunk_size()); - auto policy = create_rebound_policy(par, bound_params); + params, hpx::execution::experimental::static_chunk_size()); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -669,11 +680,11 @@ void replace_processing_units_count() // with another parameters object that exposes it { std::atomic invoked_replaced(false); - auto params = join_executor_parameters( - base_processing_units_count(), experimental::static_chunk_size()); + auto params = join_executor_parameters(base_processing_units_count(), + hpx::execution::experimental::static_chunk_size()); auto bound_params = rebind_executor_parameters( params, test_replaced_processing_units_count(invoked_replaced)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -738,11 +749,11 @@ void replace_collect_execution_parameters() { std::atomic invoked_replaced(false); - auto params = - join_executor_parameters(experimental::static_chunk_size()); + auto params = join_executor_parameters( + hpx::execution::experimental::static_chunk_size()); auto bound_params = rebind_executor_parameters(params, test_replaced_collect_execution_parameters(invoked_replaced)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -753,10 +764,11 @@ void replace_collect_execution_parameters() { std::atomic invoked_replaced(false); - auto params = join_executor_parameters(experimental::max_num_chunks()); + auto params = join_executor_parameters( + hpx::execution::experimental::max_num_chunks()); auto bound_params = rebind_executor_parameters(params, test_replaced_collect_execution_parameters(invoked_replaced)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -769,9 +781,9 @@ void replace_collect_execution_parameters() auto params = join_executor_parameters( test_replaced_collect_execution_parameters(invoked_replaced)); - auto bound_params = - rebind_executor_parameters(params, experimental::num_cores(4)); - auto policy = create_rebound_policy(par, bound_params); + auto bound_params = rebind_executor_parameters( + params, hpx::execution::experimental::num_cores(4)); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); @@ -786,7 +798,7 @@ void replace_collect_execution_parameters() join_executor_parameters(base_collect_execution_parameters()); auto bound_params = rebind_executor_parameters(params, test_replaced_collect_execution_parameters(invoked_replaced)); - auto policy = create_rebound_policy(par, bound_params); + auto policy = create_rebound_policy(hpx::execution::par, bound_params); parameters_test(policy); HPX_TEST(invoked_replaced); diff --git a/libs/core/execution_base/include/hpx/execution_base/any_sender.hpp b/libs/core/execution_base/include/hpx/execution_base/any_sender.hpp index 18b006df3daf..2b66f23ea71f 100644 --- a/libs/core/execution_base/include/hpx/execution_base/any_sender.hpp +++ b/libs/core/execution_base/include/hpx/execution_base/any_sender.hpp @@ -378,9 +378,7 @@ namespace hpx::execution::experimental::detail { any_operation_state& operator=(any_operation_state&&) = delete; any_operation_state& operator=(any_operation_state const&) = delete; - HPX_CORE_EXPORT friend void tag_invoke( - hpx::execution::experimental::start_t, - any_operation_state& os) noexcept; + void start() & noexcept; }; HPX_CXX_CORE_EXPORT template @@ -475,7 +473,7 @@ namespace hpx::execution::experimental::detail { void set_stopped() && noexcept override { - hpx::execution::experimental::set_stopped(HPX_MOVE(receiver)); + HPX_UNREACHABLE; } }; @@ -520,14 +518,13 @@ namespace hpx::execution::experimental::detail { any_receiver& operator=(any_receiver&&) = default; any_receiver& operator=(any_receiver const&) = delete; - friend void tag_invoke(hpx::execution::experimental::set_value_t, - any_receiver&& r, Ts&&... ts) noexcept + void set_value(Ts&&... ts) && noexcept { // We first move the storage to a temporary variable so that this // any_receiver is empty after this set_value. Doing // HPX_MOVE(storage.get()).set_value(...) would leave us with a // non-empty any_receiver holding a moved-from receiver. - auto moved_storage = HPX_MOVE(r.storage); + auto moved_storage = HPX_MOVE(storage); // the caller of set_value needs to forward errors to set_error try @@ -541,9 +538,16 @@ namespace hpx::execution::experimental::detail { } } - friend void tag_invoke(hpx::execution::experimental::set_error_t, - any_receiver&& /*r*/, std::exception_ptr /*ep*/) noexcept + void set_error(std::exception_ptr ep) && noexcept + { + auto moved_storage = HPX_MOVE(storage); + HPX_MOVE(moved_storage.get()).set_error(HPX_MOVE(ep)); + } + + void set_stopped() && noexcept { + auto moved_storage = HPX_MOVE(storage); + HPX_MOVE(moved_storage.get()).set_stopped(); } }; diff --git a/libs/core/execution_base/include/hpx/execution_base/stdexec_forward.hpp b/libs/core/execution_base/include/hpx/execution_base/stdexec_forward.hpp index 18c4717d4eef..7870a3748d83 100644 --- a/libs/core/execution_base/include/hpx/execution_base/stdexec_forward.hpp +++ b/libs/core/execution_base/include/hpx/execution_base/stdexec_forward.hpp @@ -111,20 +111,22 @@ namespace hpx::execution::experimental { HPX_CXX_CORE_EXPORT using stdexec::forwarding_query_t; HPX_CXX_CORE_EXPORT using stdexec::get_allocator_t; HPX_CXX_CORE_EXPORT using stdexec::get_completion_scheduler_t; - HPX_CXX_CORE_EXPORT using stdexec::get_delegatee_scheduler_t; + HPX_CXX_CORE_EXPORT using stdexec::get_delegation_scheduler_t; HPX_CXX_CORE_EXPORT using stdexec::get_domain_t; HPX_CXX_CORE_EXPORT using stdexec::get_forward_progress_guarantee_t; HPX_CXX_CORE_EXPORT using stdexec::get_scheduler_t; + HPX_CXX_CORE_EXPORT using stdexec::get_start_scheduler_t; HPX_CXX_CORE_EXPORT using stdexec::get_stop_token_t; HPX_CXX_CORE_EXPORT using stdexec::execute_may_block_caller; HPX_CXX_CORE_EXPORT using stdexec::forwarding_query; HPX_CXX_CORE_EXPORT using stdexec::get_allocator; HPX_CXX_CORE_EXPORT using stdexec::get_completion_scheduler; - HPX_CXX_CORE_EXPORT using stdexec::get_delegatee_scheduler; + HPX_CXX_CORE_EXPORT using stdexec::get_delegation_scheduler; HPX_CXX_CORE_EXPORT using stdexec::get_domain; HPX_CXX_CORE_EXPORT using stdexec::get_forward_progress_guarantee; HPX_CXX_CORE_EXPORT using stdexec::get_scheduler; + HPX_CXX_CORE_EXPORT using stdexec::get_start_scheduler; HPX_CXX_CORE_EXPORT using stdexec::get_stop_token; HPX_CXX_CORE_EXPORT using stdexec::in_place_stop_callback; @@ -196,10 +198,12 @@ namespace hpx::execution::experimental { // Execution policies HPX_CXX_CORE_EXPORT using stdexec::is_execution_policy; HPX_CXX_CORE_EXPORT using stdexec::is_execution_policy_v; - using stdexec::par; - using stdexec::par_unseq; - using stdexec::seq; - using stdexec::unseq; + + HPX_CXX_CORE_EXPORT inline constexpr stdexec::parallel_policy par{}; + HPX_CXX_CORE_EXPORT inline constexpr stdexec::parallel_unsequenced_policy + par_unseq{}; + HPX_CXX_CORE_EXPORT inline constexpr stdexec::sequenced_policy seq{}; + HPX_CXX_CORE_EXPORT inline constexpr stdexec::unsequenced_policy unseq{}; HPX_CXX_CORE_EXPORT using exec::split; HPX_CXX_CORE_EXPORT using exec::split_t; @@ -207,9 +211,6 @@ namespace hpx::execution::experimental { HPX_CXX_CORE_EXPORT using exec::ensure_started; HPX_CXX_CORE_EXPORT using exec::ensure_started_t; - HPX_CXX_CORE_EXPORT using exec::execute; - HPX_CXX_CORE_EXPORT using exec::execute_t; - // Environment queries HPX_CXX_CORE_EXPORT using exec::make_env; HPX_CXX_CORE_EXPORT using exec::make_env_t; @@ -242,9 +243,6 @@ namespace hpx::execution::experimental { HPX_CXX_CORE_EXPORT using stdexec::let_stopped; HPX_CXX_CORE_EXPORT using stdexec::let_value; - // Run loop - HPX_CXX_CORE_EXPORT using stdexec::run_loop; - // Schedule from HPX_CXX_CORE_EXPORT using stdexec::schedule_from; HPX_CXX_CORE_EXPORT using stdexec::schedule_from_t; @@ -253,9 +251,6 @@ namespace hpx::execution::experimental { HPX_CXX_CORE_EXPORT using exec::start_detached; HPX_CXX_CORE_EXPORT using exec::start_detached_t; - HPX_CXX_CORE_EXPORT using stdexec::transfer_just; - HPX_CXX_CORE_EXPORT using stdexec::transfer_just_t; - // Stop token HPX_CXX_CORE_EXPORT using stdexec::stop_callback_for_t; HPX_CXX_CORE_EXPORT using stdexec::stoppable_token; @@ -287,9 +282,12 @@ namespace hpx::execution::experimental { HPX_CXX_CORE_EXPORT using stdexec::sends_stopped; HPX_CXX_CORE_EXPORT using stdexec::value_types_of_t; - HPX_CXX_CORE_EXPORT using stdexec::transform_completion_signatures; - HPX_CXX_CORE_EXPORT using stdexec::transform_completion_signatures_of; + // New exec:: API for transform_completion_signatures (consteval function) + HPX_CXX_CORE_EXPORT using exec::transform_completion_signatures; HPX_CXX_CORE_EXPORT using exec::keep_completion; + HPX_CXX_CORE_EXPORT using exec::ignore_completion; + HPX_CXX_CORE_EXPORT using exec::transform_arguments; + HPX_CXX_CORE_EXPORT using exec::decay_arguments; // Transform sender HPX_CXX_CORE_EXPORT using stdexec::transform_sender; diff --git a/libs/core/execution_base/src/any_sender.cpp b/libs/core/execution_base/src/any_sender.cpp index 213d319dcba3..2ff4c79dbb8d 100644 --- a/libs/core/execution_base/src/any_sender.cpp +++ b/libs/core/execution_base/src/any_sender.cpp @@ -27,10 +27,9 @@ namespace hpx::execution::experimental::detail { return true; } - void tag_invoke( - hpx::execution::experimental::start_t, any_operation_state& os) noexcept + void any_operation_state::start() & noexcept { - os.storage.get().start(); + storage.get().start(); } void throw_bad_any_call(char const* class_name, char const* function_name) diff --git a/libs/core/execution_base/tests/include/algorithm_test_utils.hpp b/libs/core/execution_base/tests/include/algorithm_test_utils.hpp index eb207e7572a5..e9b7e6a4447b 100644 --- a/libs/core/execution_base/tests/include/algorithm_test_utils.hpp +++ b/libs/core/execution_base/tests/include/algorithm_test_utils.hpp @@ -224,7 +224,10 @@ struct stopped_sender template static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< - hpx::execution::experimental::set_stopped_t()>; + hpx::execution::experimental::set_stopped_t()> + { + return {}; + } }; struct stopped_sender_with_value_type @@ -259,7 +262,10 @@ struct stopped_sender_with_value_type static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_stopped_t(), - hpx::execution::experimental::set_value_t()>; + hpx::execution::experimental::set_value_t()> + { + return {}; + } }; template @@ -289,6 +295,10 @@ struct callback_receiver } }; +// Deduction guide for callback_receiver +template +callback_receiver(F, std::atomic&) -> callback_receiver; + template struct error_callback_receiver { @@ -397,7 +407,10 @@ struct error_typed_sender static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_value_t(T), - hpx::execution::experimental::set_error_t(std::exception_ptr)>; + hpx::execution::experimental::set_error_t(std::exception_ptr)> + { + return {}; + } }; template @@ -436,7 +449,10 @@ struct const_reference_sender static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_value_t(std::decay_t&), - hpx::execution::experimental::set_error_t(std::exception_ptr)>; + hpx::execution::experimental::set_error_t(std::exception_ptr)> + { + return {}; + } }; struct const_reference_error_sender @@ -473,7 +489,10 @@ struct const_reference_error_sender -> hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_value_t(), hpx::execution::experimental::set_error_t( - std::exception_ptr const&)>; + std::exception_ptr const&)> + { + return {}; + } }; struct check_exception_ptr @@ -496,13 +515,28 @@ struct check_exception_ptr } }; +// Tag struct used as a copyable first member so that stdexec's structured +// binding-based sender introspection (which extracts the sender's tag by +// passing the first member by value into __get_desc) does not require +// copying non-copyable members like std::atomic&. +struct custom_sender_descriptor_tag +{ +}; + struct custom_sender_tag_invoke { using is_sender = void; using sender_concept = hpx::execution::experimental::sender_t; + [[no_unique_address]] custom_sender_descriptor_tag __desc_tag{}; std::atomic& tag_invoke_overload_called; + explicit custom_sender_tag_invoke( + std::atomic& tag_invoke_overload_called_) noexcept + : tag_invoke_overload_called(tag_invoke_overload_called_) + { + } + template struct operation_state { @@ -522,29 +556,45 @@ struct custom_sender_tag_invoke return {std::forward(r)}; } - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - custom_sender_tag_invoke const&, Env) + template + static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< - hpx::execution::experimental::set_value_t()>; + hpx::execution::experimental::set_value_t()> + { + return {}; + } }; struct custom_sender { using is_sender = void; using sender_concept = hpx::execution::experimental::sender_t; + + [[no_unique_address]] custom_sender_descriptor_tag __desc_tag{}; std::atomic& start_called; std::atomic& connect_called; std::atomic& tag_invoke_overload_called; - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - custom_sender const&, Env&&) + custom_sender(std::atomic& start_called_, + std::atomic& connect_called_, + std::atomic& tag_invoke_overload_called_) noexcept + : start_called(start_called_) + , connect_called(connect_called_) + , tag_invoke_overload_called(tag_invoke_overload_called_) + { + } + + custom_sender(custom_sender const&) = default; + custom_sender(custom_sender&&) = default; + + template + static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_value_t(), - hpx::execution::experimental::set_error_t(std::exception_ptr)>; + hpx::execution::experimental::set_error_t(std::exception_ptr)> + { + return {}; + } template struct operation_state @@ -572,20 +622,37 @@ struct custom_sender_multi_tuple { using is_sender = void; using sender_concept = hpx::execution::experimental::sender_t; + + [[no_unique_address]] custom_sender_descriptor_tag __desc_tag{}; std::atomic& start_called; std::atomic& connect_called; std::atomic& tag_invoke_overload_called; bool expect_set_value = true; - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - custom_sender_multi_tuple const&, Env&&) + custom_sender_multi_tuple(std::atomic& start_called_, + std::atomic& connect_called_, + std::atomic& tag_invoke_overload_called_, + bool expect_set_value_ = true) noexcept + : start_called(start_called_) + , connect_called(connect_called_) + , tag_invoke_overload_called(tag_invoke_overload_called_) + , expect_set_value(expect_set_value_) + { + } + + custom_sender_multi_tuple(custom_sender_multi_tuple const&) = default; + custom_sender_multi_tuple(custom_sender_multi_tuple&&) = default; + + template + static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_value_t(int), hpx::execution::experimental::set_value_t(std::string), - hpx::execution::experimental::set_error_t(std::exception_ptr)>; + hpx::execution::experimental::set_error_t(std::exception_ptr)> + { + return {}; + } template struct operation_state @@ -603,7 +670,8 @@ struct custom_sender_multi_tuple } else { - hpx::execution::experimental::set_value(std::move(r), "err"); + hpx::execution::experimental::set_value( + std::move(r), std::string("err")); } } }; @@ -622,12 +690,28 @@ struct custom_typed_sender { using is_sender = void; using sender_concept = hpx::execution::experimental::sender_t; + + [[no_unique_address]] custom_sender_descriptor_tag __desc_tag{}; std::decay_t x; std::atomic& start_called; std::atomic& connect_called; std::atomic& tag_invoke_overload_called; + template + custom_typed_sender(U&& x_, std::atomic& start_called_, + std::atomic& connect_called_, + std::atomic& tag_invoke_overload_called_) noexcept + : x(std::forward(x_)) + , start_called(start_called_) + , connect_called(connect_called_) + , tag_invoke_overload_called(tag_invoke_overload_called_) + { + } + + custom_typed_sender(custom_typed_sender const&) = default; + custom_typed_sender(custom_typed_sender&&) = default; + template struct operation_state { @@ -651,13 +735,14 @@ struct custom_typed_sender std::move(s.x), s.start_called, std::forward(r)}; } - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - custom_typed_sender const&, Env&&) + template + static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< hpx::execution::experimental::set_value_t(T), - hpx::execution::experimental::set_error_t(std::exception_ptr)>; + hpx::execution::experimental::set_error_t(std::exception_ptr)> + { + return {}; + } }; struct custom_sender2 : custom_sender @@ -858,11 +943,10 @@ namespace my_namespace { { std::decay_t r; - friend void tag_invoke(hpx::execution::experimental::start_t, - operation_state& os) noexcept + void start() & noexcept { - hpx::execution::experimental::set_value(std::move(os.r)); - }; + hpx::execution::experimental::set_value(std::move(r)); + } }; template @@ -871,23 +955,21 @@ namespace my_namespace { { return operation_state{std::forward(r)}; } - friend env_with_scheduler tag_invoke( - hpx::execution::experimental::get_env_t, - my_sender const&) noexcept + env_with_scheduler get_env() const noexcept { return {}; } - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - my_sender const&, Env) + template + static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< - hpx::execution::experimental::set_value_t()>; + hpx::execution::experimental::set_value_t()> + { + return {}; + } }; - friend my_sender tag_invoke( - hpx::execution::experimental::schedule_t, my_scheduler_template) + my_sender schedule() const noexcept { return {}; } @@ -927,12 +1009,13 @@ namespace my_namespace { return {std::forward(r)}; } - template - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - my_sender const&, Env) + template + static consteval auto get_completion_signatures() noexcept -> hpx::execution::experimental::completion_signatures< - hpx::execution::experimental::set_value_t()>; + hpx::execution::experimental::set_value_t()> + { + return {}; + } }; // This overload should not be chosen by test_adl_isolation below. We make diff --git a/libs/core/execution_base/tests/include/coroutine_task.hpp b/libs/core/execution_base/tests/include/coroutine_task.hpp index d54188a4bda1..e092b773239d 100644 --- a/libs/core/execution_base/tests/include/coroutine_task.hpp +++ b/libs/core/execution_base/tests/include/coroutine_task.hpp @@ -94,11 +94,10 @@ struct default_task_context_impl template friend struct default_awaiter_context; - friend auto tag_invoke(hpx::execution::experimental::get_stop_token_t, - default_task_context_impl const& self) noexcept + auto query(hpx::execution::experimental::get_stop_token_t) const noexcept -> hpx::experimental::in_place_stop_token { - return self.stop_token_; + return stop_token_; } public: @@ -188,37 +187,37 @@ struct default_awaiter_context struct default_awaiter_context { - explicit default_awaiter_context(default_task_context_impl&, auto&) noexcept - { - } - - template >> + template explicit default_awaiter_context( default_task_context_impl& self, ParentPromise& parent) { // Register a callback that will request stop on this basic_task's // stop_source when stop is requested on the parent coroutine's stop // token. - using stop_token_t = hpx::execution::experimental::stop_token_of_t< - hpx::execution::experimental::env_of_t>; - using stop_callback_t = - typename stop_token_t::template callback_type; - - if constexpr (std::is_same_v) + if constexpr (requires { + hpx::execution::experimental::get_env(parent); + }) { - self.stop_token_ = hpx::execution::experimental::get_stop_token( - hpx::execution::experimental::get_env(parent)); - } - else if (auto token = hpx::execution::experimental::get_stop_token( - hpx::execution::experimental::get_env(parent)); - token.stop_possible()) - { - stop_callback_.emplace( - std::move(token), forward_stop_request{stop_source_}); - self.stop_token_ = stop_source_.get_token(); + using stop_token_t = hpx::execution::experimental::stop_token_of_t< + hpx::execution::experimental::env_of_t>; + using stop_callback_t = + typename stop_token_t::template callback_type< + forward_stop_request>; + + if constexpr (std::is_same_v) + { + self.stop_token_ = hpx::execution::experimental::get_stop_token( + hpx::execution::experimental::get_env(parent)); + } + else if (auto token = hpx::execution::experimental::get_stop_token( + hpx::execution::experimental::get_env(parent)); + token.stop_possible()) + { + stop_callback_.emplace( + std::move(token), forward_stop_request{stop_source_}); + self.stop_token_ = stop_source_.get_token(); + } } } @@ -336,10 +335,9 @@ class basic_task } using context_t = typename Context::template promise_context_t<_promise>; - friend context_t tag_invoke(hpx::execution::experimental::get_env_t, - _promise const& self) noexcept + context_t get_env() const noexcept { - return self.context_; + return context_; } context_t context_; }; diff --git a/libs/core/execution_base/tests/unit/CMakeLists.txt b/libs/core/execution_base/tests/unit/CMakeLists.txt index f308956bea2b..10d0bfbfbae8 100644 --- a/libs/core/execution_base/tests/unit/CMakeLists.txt +++ b/libs/core/execution_base/tests/unit/CMakeLists.txt @@ -22,26 +22,24 @@ if(HPX_WITH_CXX20_COROUTINES) set(tests ${tests} coroutine_traits coroutine_utils) endif() -if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - foreach(test ${tests}) - set(sources ${test}.cpp) +foreach(test ${tests}) + set(sources ${test}.cpp) - source_group("Source Files" FILES ${sources}) + source_group("Source Files" FILES ${sources}) - # add example executable - add_hpx_executable( - ${test}_test INTERNAL_FLAGS - SOURCES ${sources} - NOLIBS - DEPENDENCIES hpx_core ${BOOST_UNDERLYING_THREAD_LIBRARY} - EXCLUDE_FROM_ALL - FOLDER "Tests/Unit/Modules/Core/ExecutionBase" - ) + # add example executable + add_hpx_executable( + ${test}_test INTERNAL_FLAGS + SOURCES ${sources} + NOLIBS + DEPENDENCIES hpx_core ${BOOST_UNDERLYING_THREAD_LIBRARY} + EXCLUDE_FROM_ALL + FOLDER "Tests/Unit/Modules/Core/ExecutionBase" + ) - target_link_libraries(${test}_test PRIVATE hpx_execution_test_utilities) + target_link_libraries(${test}_test PRIVATE hpx_execution_test_utilities) - add_hpx_unit_test("modules.execution_base" ${test} ${${test}_PARAMETERS}) - target_compile_definitions(${test}_test PRIVATE -DHPX_MODULE_STATIC_LINKING) + add_hpx_unit_test("modules.execution_base" ${test} ${${test}_PARAMETERS}) + target_compile_definitions(${test}_test PRIVATE -DHPX_MODULE_STATIC_LINKING) - endforeach() -endif() +endforeach() diff --git a/libs/core/execution_base/tests/unit/any_sender.cpp b/libs/core/execution_base/tests/unit/any_sender.cpp index 911bb7b075f7..4c8ff6197088 100644 --- a/libs/core/execution_base/tests/unit/any_sender.cpp +++ b/libs/core/execution_base/tests/unit/any_sender.cpp @@ -89,13 +89,12 @@ struct non_copyable_sender std::decay_t r; hpx::tuple...> ts; - friend void tag_invoke( - hpx::execution::experimental::start_t, operation_state& os) noexcept + void start() & noexcept { hpx::invoke_fused( hpx::bind_front( - hpx::execution::experimental::set_value, std::move(os.r)), - std::move(os.ts)); + hpx::execution::experimental::set_value, std::move(r)), + std::move(ts)); }; }; @@ -148,13 +147,12 @@ struct example_sender std::decay_t r; hpx::tuple...> ts; - friend void tag_invoke( - hpx::execution::experimental::start_t, operation_state& os) noexcept + void start() & noexcept { hpx::invoke_fused( hpx::bind_front( - hpx::execution::experimental::set_value, std::move(os.r)), - std::move(os.ts)); + hpx::execution::experimental::set_value, std::move(r)), + std::move(ts)); }; }; @@ -230,39 +228,34 @@ struct large_sender : example_sender struct error_receiver { - std::atomic& set_error_called; + using receiver_concept = hpx::execution::experimental::receiver_t; - struct is_receiver - { - }; + std::atomic& set_error_called; - friend void tag_invoke(hpx::execution::experimental::set_error_t, - error_receiver&& r, std::exception_ptr&& e) noexcept + void set_error(std::exception_ptr e) && noexcept { try { std::rethrow_exception(std::move(e)); } - catch (std::runtime_error const& e) + catch (std::runtime_error const& re) { - HPX_TEST_EQ(std::string(e.what()), std::string("error")); + HPX_TEST_EQ(std::string(re.what()), std::string("error")); } catch (...) { HPX_TEST(false); } - r.set_error_called = true; + set_error_called = true; } - friend void tag_invoke( - hpx::execution::experimental::set_stopped_t, error_receiver&&) noexcept + void set_stopped() && noexcept { HPX_TEST(false); - }; + } template - friend void tag_invoke(hpx::execution::experimental::set_value_t, - error_receiver&&, Ts&&...) noexcept + void set_value(Ts&&...) && noexcept { HPX_TEST(false); } diff --git a/libs/core/execution_base/tests/unit/basic_operation_state.cpp b/libs/core/execution_base/tests/unit/basic_operation_state.cpp index 2b5b44ee24b8..e1dd47ae0cd2 100644 --- a/libs/core/execution_base/tests/unit/basic_operation_state.cpp +++ b/libs/core/execution_base/tests/unit/basic_operation_state.cpp @@ -32,12 +32,12 @@ namespace mylib { struct state_2 { - friend void tag_invoke(ex::start_t, state_2&) {} + void start() & {} }; struct state_3 { - friend void tag_invoke(ex::start_t, state_3&) noexcept + void start() & noexcept { start_called = true; } @@ -45,33 +45,31 @@ namespace mylib { struct state_4 { + void start() & {} }; - void tag_invoke(ex::start_t, state_4&) {} - struct state_5 { - }; - - void tag_invoke(ex::start_t, state_5&) noexcept - { - start_called = true; - } + void start() & noexcept + { + start_called = true; + } + }; // Added semicolon here template struct state { - friend void tag_invoke(ex::start_t, state&&) noexcept(Noexcept) + void start() && noexcept(Noexcept) { HPX_TEST(false); } - friend void tag_invoke(ex::start_t, state&) noexcept(Noexcept) + void start() & noexcept(Noexcept) { started = 1; } - friend void tag_invoke(ex::start_t, state const&) noexcept(Noexcept) + void start() const& noexcept(Noexcept) { started = 2; } @@ -83,7 +81,7 @@ namespace mylib { ~indestructible_state() {} public: - friend void tag_invoke(ex::start_t, indestructible_state&) noexcept + void start() & noexcept { started = 3; } @@ -109,59 +107,44 @@ int main() { // verify test class - static_assert(noexcept( - tag_invoke(ex::start, std::declval>()))); - static_assert(noexcept( - tag_invoke(ex::start, std::declval&&>()))); - static_assert(noexcept( - tag_invoke(ex::start, std::declval&>()))); - static_assert(noexcept( - tag_invoke(ex::start, std::declval const&>()))); + static_assert(noexcept(std::declval>().start())); + static_assert(noexcept(std::declval&&>().start())); + static_assert(noexcept(std::declval&>().start())); + static_assert( + noexcept(std::declval const&>().start())); // rvalues can't be used via the start CPO static_assert(!hpx::is_invocable_v>); static_assert(!hpx::is_invocable_v&&>); // lvalues can be used via the start CPO and don't throw - static_assert(noexcept(hpx::functional::tag_invoke( - ex::start, std::declval&>()))); - static_assert(noexcept(hpx::functional::tag_invoke( - ex::start, std::declval const&>()))); + static_assert(noexcept(ex::start(std::declval&>()))); static_assert( std::is_nothrow_invocable_v&>); - static_assert(std::is_nothrow_invocable_v const&>); } { // verify test class - static_assert(!noexcept( - tag_invoke(ex::start, std::declval>()))); - static_assert(!noexcept( - tag_invoke(ex::start, std::declval&&>()))); - static_assert(!noexcept( - tag_invoke(ex::start, std::declval&>()))); - static_assert(!noexcept( - tag_invoke(ex::start, std::declval const&>()))); - - // none of the operations work via the start CPO if they'd throw - /*TODO: Check if the following way of invoking the start cpo leads to - * the required checks by the execution.op_state concept check. That - * check goes through the operator() of start_t but I am not sure if - * we ever reach that point when calling the tag_invoke directly. - */ - // static_assert(!hpx::is_invocable_v>); - // static_assert(!hpx::is_invocable_v&&>); - // static_assert(!hpx::is_invocable_v&>); - // static_assert( - // !hpx::is_invocable_v const&>); + static_assert(!noexcept(std::declval>().start())); + static_assert(!noexcept(std::declval&&>().start())); + static_assert(!noexcept(std::declval&>().start())); + static_assert( + !noexcept(std::declval const&>().start())); + + // stdexec's start_t constraint only checks .start() member exists; + // noexcept is enforced via static_assert inside the body, not SFINAE. + // So start_t IS invocable even on non-noexcept operation states. + static_assert(hpx::is_invocable_v&>); + static_assert( + hpx::is_invocable_v const&>); + // rvalues/temporaries still not invocable (start_t takes _Op&) + static_assert(!hpx::is_invocable_v>); + static_assert(!hpx::is_invocable_v&&>); } { - static_assert(noexcept(hpx::functional::tag_invoke( - ex::start, std::declval()))); - static_assert(noexcept(hpx::functional::tag_invoke( - ex::start, std::declval()))); + static_assert( + noexcept(ex::start(std::declval()))); static_assert(std::is_nothrow_invocable_v); diff --git a/libs/core/execution_base/tests/unit/completion_signatures.cpp b/libs/core/execution_base/tests/unit/completion_signatures.cpp index 1ab6f39c6272..17b03f275c1c 100644 --- a/libs/core/execution_base/tests/unit/completion_signatures.cpp +++ b/libs/core/execution_base/tests/unit/completion_signatures.cpp @@ -235,7 +235,7 @@ struct sender_1 } }; -template +template constexpr auto tag_invoke(ex::get_completion_signatures_t, sender_1 const&, Env = Env{}) noexcept -> Signatures { diff --git a/libs/core/execution_base/tests/unit/coroutine_utils.cpp b/libs/core/execution_base/tests/unit/coroutine_utils.cpp index 56839ffc4b76..366dac548b10 100644 --- a/libs/core/execution_base/tests/unit/coroutine_utils.cpp +++ b/libs/core/execution_base/tests/unit/coroutine_utils.cpp @@ -101,6 +101,11 @@ struct awaitable_sender_4 { using promise_type = promise; + hpx::execution::experimental::empty_env get_env() const noexcept + { + return {}; + } + private: template friend awaiter tag_invoke(hpx::execution::experimental::as_awaitable_t, @@ -112,6 +117,11 @@ struct awaitable_sender_4 struct awaitable_sender_5 { + hpx::execution::experimental::empty_env get_env() const noexcept + { + return {}; + } + private: template friend awaiter tag_invoke(hpx::execution::experimental::as_awaitable_t, @@ -127,21 +137,13 @@ struct recv_set_value using receiver_concept = hpx::execution::experimental::receiver_t; using dependent = awaiter; - friend void tag_invoke(hpx::execution::experimental::set_value_t, - recv_set_value, - decltype(std::declval().await_ready())) noexcept - { - } - friend void tag_invoke( - hpx::execution::experimental::set_stopped_t, recv_set_value) noexcept - { - } - friend void tag_invoke(hpx::execution::experimental::set_error_t, - recv_set_value, std::exception_ptr) noexcept + void set_value( + decltype(std::declval().await_ready())) && noexcept { } - friend dependent tag_invoke( - hpx::execution::experimental::get_env_t, recv_set_value const&) noexcept + void set_stopped() && noexcept {} + void set_error(std::exception_ptr) && noexcept {} + dependent get_env() const noexcept { return {}; } @@ -237,14 +239,16 @@ int main() { static_assert(ex::is_sender_v>); static_assert(ex::is_sender_v); - static_assert(ex::is_sender_v); + // awaitable_sender_4 and awaitable_sender_5 are not standalone senders + // under stdexec - they require with_awaitable_senders context } // env promise { static_assert(is_sender_with_env_v>); static_assert(is_sender_with_env_v); - static_assert(is_sender_with_env_v); + // awaitable_sender_4 and awaitable_sender_5 are not standalone senders + // under stdexec - they require with_awaitable_senders context } try diff --git a/libs/core/execution_base/tests/unit/get_env.cpp b/libs/core/execution_base/tests/unit/get_env.cpp index 1212fd72ad56..50d37361a1ce 100644 --- a/libs/core/execution_base/tests/unit/get_env.cpp +++ b/libs/core/execution_base/tests/unit/get_env.cpp @@ -40,6 +40,7 @@ namespace mylib { { // clang-format off template + requires requires(Env const& e, receiver_env_t q) { e.query(q); } decltype(auto) operator()(Env const& e) const noexcept { return e.query(*this); @@ -62,8 +63,9 @@ namespace mylib { }; // clang-format off - auto env4 = ex::env( - std::move(env3), ex::prop(receiver_env, std::string("42"))); + auto env4 = ex::make_env( + ex::prop(receiver_env, 42), + ex::prop(receiver_env, std::string("42"))); // clang-format on using env4_t = decltype(env4); @@ -73,27 +75,16 @@ namespace mylib { decltype(auto) get_env() const noexcept { - receiver_3 rcv; - - /* Due to https://github.com/llvm/llvm-project/issues/88077 - * The following line never compiles, and if it does, it misbehaves. - * return ex::env( - * ex::prop(receiver_env, std::string("42")), - * ex::get_env(rcv) - * ); - * - * The following is a workaround */ - - auto e = ex::get_env(rcv); - auto p = ex::prop(receiver_env, std::string("42")); - - return ex::env(std::move(e), std::move(p)); + // Return flat env with both props instead of nesting + return ex::make_env(ex::prop(receiver_env, 42), + ex::prop(receiver_env, std::string("42"))); } }; inline constexpr struct receiver_env1_t final : ex::forwarding_query_t { template + requires requires(Env const& e, receiver_env1_t q) { e.query(q); } decltype(auto) operator()(Env const& e) const noexcept { return e.query(*this); @@ -102,7 +93,9 @@ namespace mylib { // clang-format off auto env5 = - ex::env(std::move(env3), ex::prop(receiver_env1, std::string("42"))); + ex::make_env( + ex::prop(receiver_env, 42), + ex::prop(receiver_env1, std::string("42"))); // clang-format on using env5_t = decltype(env5); @@ -112,15 +105,9 @@ namespace mylib { decltype(auto) get_env() const noexcept { - receiver_3 rcv; - /* Same as receiver_4 - * This would cause the compiler to crash: - * return ex::env( - * ex::get_env(rcv), ex::prop(receiver_env1, std::string("42"))); - * */ - auto e = ex::get_env(rcv); - auto p = ex::prop(receiver_env1, std::string("42")); - return ex::env(std::move(e), std::move(p)); + // Return flat env with both props instead of nesting + return ex::make_env(ex::prop(receiver_env, 42), + ex::prop(receiver_env1, std::string("42"))); } }; } // namespace mylib @@ -151,9 +138,9 @@ int main() // The resulting env_ = env(env1, env2) will query env1 first and env2 // in that order, in order to find the resulting value of some query. // In cases when both env1 and env2 support the same query, as seen here - // with receiver_env the result of env1 is picked. + // with receiver_env, the new stdexec API returns the last value. auto env = ex::get_env(rcv); - HPX_TEST_EQ(mylib::receiver_env(env), 42); + HPX_TEST_EQ(mylib::receiver_env(env), std::string("42")); } { mylib::receiver_5 rcv; diff --git a/libs/core/execution_base/tests/unit/stdexec.cpp b/libs/core/execution_base/tests/unit/stdexec.cpp index b9ff9f6028e8..b51891ff5f25 100644 --- a/libs/core/execution_base/tests/unit/stdexec.cpp +++ b/libs/core/execution_base/tests/unit/stdexec.cpp @@ -17,6 +17,8 @@ int main() auto result = hpx::execution::experimental::sync_wait(std::move(x)); HPX_TEST(result.has_value()); + if (!result) + return hpx::util::report_errors(); auto [a] = std::move(*result); HPX_TEST(a == 42); diff --git a/libs/core/executors/include/hpx/executors/execute_on.hpp b/libs/core/executors/include/hpx/executors/execute_on.hpp index 2959db0a01b3..45bdd34ef731 100644 --- a/libs/core/executors/include/hpx/executors/execute_on.hpp +++ b/libs/core/executors/include/hpx/executors/execute_on.hpp @@ -56,6 +56,13 @@ namespace hpx::execution::experimental { using base_scheduler_type = std::decay_t; using policy_type = std::decay_t; + scheduler_and_policy(scheduler_and_policy const&) = default; + scheduler_and_policy(scheduler_and_policy&&) noexcept = default; + scheduler_and_policy& operator=(scheduler_and_policy const&) = default; + scheduler_and_policy& operator=( + scheduler_and_policy&&) noexcept = default; + ~scheduler_and_policy() = default; + template scheduler_and_policy(Scheduler_&& sched, ExPolicy_&& policy) : base_scheduler_type(HPX_FORWARD(Scheduler_, sched)) @@ -74,18 +81,10 @@ namespace hpx::execution::experimental { } // Needed for this to be a scheduler under the p2300 definition - friend constexpr - typename Scheduler::template sender - tag_invoke(schedule_t, scheduler_and_policy const& sp) - { - return {sp}; - } - - friend constexpr - typename Scheduler::template sender - tag_invoke(schedule_t, scheduler_and_policy&& sp) + constexpr typename Scheduler::template sender + schedule() const { - return {HPX_MOVE(sp)}; + return {*this}; } policy_type policy; diff --git a/libs/core/executors/include/hpx/executors/explicit_scheduler_executor.hpp b/libs/core/executors/include/hpx/executors/explicit_scheduler_executor.hpp index 27d6385c6f9d..3325d8350500 100644 --- a/libs/core/executors/include/hpx/executors/explicit_scheduler_executor.hpp +++ b/libs/core/executors/include/hpx/executors/explicit_scheduler_executor.hpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -38,14 +39,10 @@ namespace hpx::execution::experimental { constexpr explicit_scheduler_executor() = default; - // clang-format off - template , explicit_scheduler_executor> && - hpx::execution::experimental::is_scheduler_v>> - // clang-format on + template + requires(!std::is_same_v, + explicit_scheduler_executor> && + hpx::execution::experimental::is_scheduler_v) constexpr explicit explicit_scheduler_executor(Scheduler&& sched) : sched_(HPX_FORWARD(Scheduler, sched)) { @@ -166,12 +163,8 @@ namespace hpx::execution::experimental { // BulkTwoWayExecutor interface // Integral shape overload - passes integral directly to bulk - // clang-format off - template - )> - // clang-format on + template + requires(std::is_integral_v) friend decltype(auto) tag_invoke( hpx::parallel::execution::bulk_async_execute_t, explicit_scheduler_executor const& exec, F&& f, S const& shape, @@ -182,46 +175,42 @@ namespace hpx::execution::experimental { } // Range shape overload - // clang-format off - template - )> - // clang-format on + template + requires(!std::is_integral_v) friend decltype(auto) tag_invoke( hpx::parallel::execution::bulk_async_execute_t, explicit_scheduler_executor const& exec, F&& f, S const& shape, Ts&&... ts) { - using shape_element = - typename hpx::traits::range_traits::value_type; + using shape_element = hpx::traits::range_traits::value_type; using result_type = hpx::util::detail::invoke_deferred_result_t; - /* A boolean as result_type is disallowed because the elements of a - * vector cannot be modified concurrently. */ - static_assert(!std::is_same_v, - "Using an invocable that returns a boolean with " - "explicit_scheduler_executor::bulk_async_execution " - "can result in data races!"); - if constexpr (std::is_void_v) { // stdexec::bulk requires integral shape and execution policy - using size_type = decltype(util::size(shape)); - size_type const n = util::size(shape); + using size_type = decltype(std::ranges::size(shape)); + size_type const n = std::ranges::size(shape); return bulk(schedule(exec.sched_), n, - [shape, f = HPX_FORWARD(F, f), - ... args = HPX_FORWARD(Ts, ts)](size_type i) mutable { - auto it = util::begin(shape); - std::advance(it, i); - HPX_INVOKE(f, *it, args...); + [shape, + bound_f = hpx::bind_back(HPX_FORWARD(F, f), + HPX_FORWARD(Ts, ts)...)](size_type i) mutable { + auto it = std::ranges::begin(shape); + std::ranges::advance(it, i); + HPX_INVOKE(bound_f, *it); }); } else { - using size_type = decltype(util::size(shape)); - size_type const shape_size = util::size(shape); + // A boolean as result_type is disallowed because the elements + // of a vector cannot be modified concurrently. + static_assert(!std::is_same_v, + "Using an invocable that returns a boolean with " + "explicit_scheduler_executor::bulk_async_execution " + "can result in data races!"); + + using size_type = decltype(std::ranges::size(shape)); + size_type const shape_size = std::ranges::size(shape); using result_vector_type = std::vector; result_vector_type result_vector(shape_size); @@ -229,7 +218,7 @@ namespace hpx::execution::experimental { auto f_wrapper = [](size_type const i, result_vector_type& result_vector, S const& shape, F& f, Ts&... ts) { - auto it = std::begin(shape); + auto it = std::ranges::begin(shape); result_vector[i] = HPX_INVOKE(f, *std::next(it, i), ts...); }; @@ -248,12 +237,8 @@ namespace hpx::execution::experimental { } // Integral shape overload - passes integral directly - // clang-format off - template - )> - // clang-format on + template + requires(std::is_integral_v) friend decltype(auto) tag_invoke( hpx::parallel::execution::bulk_sync_execute_t, explicit_scheduler_executor const& exec, F&& f, S const& shape, @@ -265,12 +250,8 @@ namespace hpx::execution::experimental { } // Range shape overload - // clang-format off - template - )> - // clang-format on + template + requires(!std::is_integral_v) friend decltype(auto) tag_invoke( hpx::parallel::execution::bulk_sync_execute_t, explicit_scheduler_executor const& exec, F&& f, S const& shape, @@ -282,12 +263,8 @@ namespace hpx::execution::experimental { } // Integral shape overload - passes integral directly to bulk - // clang-format off - template - )> - // clang-format on + template + requires(std::is_integral_v) friend auto tag_invoke(hpx::parallel::execution::bulk_then_execute_t, explicit_scheduler_executor const& exec, F&& f, S const& shape, Future&& predecessor, Ts&&... ts) @@ -301,12 +278,8 @@ namespace hpx::execution::experimental { } // Range shape overload - // clang-format off - template - )> - // clang-format on + template + requires(!std::is_integral_v) friend auto tag_invoke(hpx::parallel::execution::bulk_then_execute_t, explicit_scheduler_executor const& exec, F&& f, S const& shape, Future&& predecessor, Ts&&... ts) @@ -321,16 +294,17 @@ namespace hpx::execution::experimental { auto pre_req = when_all(keep_future(HPX_FORWARD(Future, predecessor))); - using size_type = decltype(util::size(shape)); - size_type const n = util::size(shape); + using size_type = decltype(std::ranges::size(shape)); + size_type const n = std::ranges::size(shape); return continues_on(HPX_MOVE(pre_req), exec.sched_) | bulk(n, - [shape, f = HPX_FORWARD(F, f), - ... args = HPX_FORWARD(Ts, ts)]( + [shape, + bound_f = hpx::bind_back( + HPX_FORWARD(F, f), HPX_FORWARD(Ts, ts)...)]( size_type i, auto&... receiver_args) mutable { - auto it = util::begin(shape); - std::advance(it, i); - HPX_INVOKE(f, *it, args..., receiver_args...); + auto it = std::ranges::begin(shape); + std::ranges::advance(it, i); + HPX_INVOKE(bound_f, *it, receiver_args...); }); } diff --git a/libs/core/executors/include/hpx/executors/scheduler_executor.hpp b/libs/core/executors/include/hpx/executors/scheduler_executor.hpp index cc84cf09a176..7d72ae5bde59 100644 --- a/libs/core/executors/include/hpx/executors/scheduler_executor.hpp +++ b/libs/core/executors/include/hpx/executors/scheduler_executor.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -31,11 +32,11 @@ namespace hpx::execution::experimental { HPX_CXX_CORE_EXPORT template auto captured_args_then(F&& f, Ts&&... ts) { - return [f = HPX_FORWARD(F, f), ... ts = HPX_FORWARD(Ts, ts)]( + return [bound_f = hpx::bind_back( + HPX_FORWARD(F, f), HPX_FORWARD(Ts, ts)...)]( auto i, auto&& predecessor, auto& v) mutable { - v[i] = HPX_INVOKE(HPX_FORWARD(F, f), i, - HPX_FORWARD(decltype(predecessor), predecessor), - HPX_FORWARD(Ts, ts)...); + v[i] = HPX_INVOKE(bound_f, i, + HPX_FORWARD(decltype(predecessor), predecessor)); }; } } // namespace detail @@ -52,10 +53,10 @@ namespace hpx::execution::experimental { constexpr scheduler_executor() = default; - template && - !std::is_same_v, scheduler_executor>>> + template + requires( + !std::is_same_v, scheduler_executor> && + hpx::execution::experimental::is_scheduler_v) constexpr explicit scheduler_executor(Scheduler&& sched) : sched_(HPX_FORWARD(Scheduler, sched)) { @@ -172,35 +173,39 @@ namespace hpx::execution::experimental { friend auto tag_invoke(hpx::parallel::execution::bulk_async_execute_t, scheduler_executor const& exec, F&& f, S const& shape, Ts&&... ts) { - using shape_element = - typename hpx::traits::range_traits::value_type; + using shape_element = hpx::traits::range_traits::value_type; using result_type = hpx::util::detail::invoke_deferred_result_t; + // hpx::execution::experimental::bulk requires integral shape + using size_type = decltype(std::ranges::size(shape)); + size_type const n = std::ranges::size(shape); + if constexpr (std::is_void_v) { - // hpx::execution::experimental::bulk requires integral shape - // and execution policy - using size_type = decltype(hpx::util::size(shape)); - size_type const n = hpx::util::size(shape); return make_future(bulk(schedule(exec.sched_), n, - [shape, f = HPX_FORWARD(F, f), - ... args = HPX_FORWARD(Ts, ts)](size_type i) mutable { - auto it = hpx::util::begin(shape); - std::advance(it, i); - HPX_INVOKE(f, *it, args...); + [shape, + bound_f = hpx::bind_back(HPX_FORWARD(F, f), + HPX_FORWARD(Ts, ts)...)](size_type i) mutable { + auto it = std::ranges::begin(shape); + std::ranges::advance(it, i); + HPX_INVOKE(bound_f, *it); })); } else { + // A boolean as result_type is disallowed because the elements + // of a vector cannot be modified concurrently. + static_assert(!std::is_same_v, + "Using an invocable that returns a boolean with " + "scheduler_executor::bulk_async_execution can result in " + "data races!"); + using promise_vector_type = std::vector>; using result_vector_type = std::vector>; - using size_type = decltype(hpx::util::size(shape)); - size_type const n = hpx::util::size(shape); - promise_vector_type promises(n); result_vector_type results; results.reserve(n); @@ -215,8 +220,8 @@ namespace hpx::execution::experimental { S const& shape, Ts&... ts) { hpx::detail::try_catch_exception_ptr( [&]() mutable { - auto it = hpx::util::begin(shape); - std::advance(it, i); + auto it = std::ranges::begin(shape); + std::ranges::advance(it, i); promises[i].set_value(HPX_INVOKE(f, *it, ts...)); }, [&](std::exception_ptr&& ep) { @@ -239,26 +244,25 @@ namespace hpx::execution::experimental { friend auto tag_invoke(hpx::parallel::execution::bulk_sync_execute_t, scheduler_executor const& exec, F&& f, S const& shape, Ts&&... ts) { - using shape_element = - typename hpx::traits::range_traits::value_type; + using shape_element = hpx::traits::range_traits::value_type; using result_type = hpx::util::detail::invoke_deferred_result_t; // hpx::execution::experimental::bulk requires integral shape - // and execution policy - using size_type = decltype(hpx::util::size(shape)); - size_type const n = hpx::util::size(shape); + using size_type = decltype(std::ranges::size(shape)); + size_type const n = std::ranges::size(shape); + return hpx::util::void_guard(), // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - *hpx::this_thread::experimental::sync_wait( - bulk(schedule(exec.sched_), n, - [shape, f = HPX_FORWARD(F, f), - ... args = HPX_FORWARD(Ts, ts)]( - size_type i) mutable { - auto it = hpx::util::begin(shape); - std::advance(it, i); - HPX_INVOKE(f, *it, args...); - })); + *hpx::this_thread::experimental::sync_wait(bulk( + schedule(exec.sched_), n, + [shape, + bound_f = hpx::bind_back(HPX_FORWARD(F, f), + HPX_FORWARD(Ts, ts)...)](size_type i) mutable { + auto it = std::ranges::begin(shape); + std::ranges::advance(it, i); + HPX_INVOKE(bound_f, *it); + })); } template @@ -286,10 +290,17 @@ namespace hpx::execution::experimental { } else { + // A boolean as result_type is disallowed because the elements + // of a vector cannot be modified concurrently. + static_assert(!std::is_same_v, + "Using an invocable that returns a boolean with " + "scheduler_executor::bulk_then_execute can result in " + "data races!"); + // the overall return value is future> - auto pre_req = - when_all(keep_future(HPX_FORWARD(Future, predecessor)), - just(std::vector(hpx::util::size(shape)))); + auto pre_req = when_all( + keep_future(HPX_FORWARD(Future, predecessor)), + just(std::vector(std::ranges::size(shape)))); auto loop = bulk(continues_on(HPX_MOVE(pre_req), exec.sched_), shape, diff --git a/libs/core/executors/include/hpx/executors/thread_pool_scheduler.hpp b/libs/core/executors/include/hpx/executors/thread_pool_scheduler.hpp index 570733dcd4d5..737c6de2aefa 100644 --- a/libs/core/executors/include/hpx/executors/thread_pool_scheduler.hpp +++ b/libs/core/executors/include/hpx/executors/thread_pool_scheduler.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -20,9 +21,13 @@ #include #include #include +#include +#include #include +#include #include #include +#include #include #include @@ -31,10 +36,11 @@ // Forward declaration namespace hpx::execution::experimental::detail { - template + + HPX_CXX_CORE_EXPORT template class thread_pool_bulk_sender; -} +} // namespace hpx::execution::experimental::detail namespace hpx::execution::experimental { @@ -60,13 +66,13 @@ namespace hpx::execution::experimental { } // namespace detail // Forward declarations - template + HPX_CXX_CORE_EXPORT template struct thread_pool_policy_scheduler; // Forward declarations for domain system // Concept to match bulk sender types - template + HPX_CXX_CORE_EXPORT template concept bulk_chunked_or_unchunked_sender = hpx::execution::experimental::stdexec_internal::__sender_for || @@ -75,10 +81,11 @@ namespace hpx::execution::experimental { hpx::execution::experimental::stdexec_internal::__sender_for; - // Domain customization for stdexec bulk operations + // Domain customization for stdexec bulk operations and sync_wait. // Following the stdexec parallel_scheduler pattern (set_value_t tag-based). - template - struct thread_pool_domain : hpx::execution::experimental::default_domain + HPX_CXX_CORE_EXPORT template + struct thread_pool_domain + : hpx::execution::experimental::detail::sync_wait_domain { // transform_sender for bulk operations // (following stdexec parallel_scheduler pattern) @@ -97,8 +104,7 @@ namespace hpx::execution::experimental { auto&& [tag, data, child] = sndr; auto&& [pol, shape, f] = data; - auto iota_shape = - hpx::util::counting_shape(decltype(shape){0}, shape); + auto iota_shape = hpx::util::counting_shape(shape); // bulk_t and bulk_unchunked_t use unchunked mode (f(index, ...values)) // bulk_chunked_t uses chunked mode (f(begin, end, ...values)) @@ -108,8 +114,8 @@ namespace hpx::execution::experimental { return hpx::execution::experimental::detail:: thread_pool_bulk_sender, - std::decay_t, - std::decay_t, is_chunked>(HPX_MOVE(sched), + decltype(iota_shape), std::decay_t, + is_chunked>(HPX_MOVE(sched), HPX_FORWARD(decltype(child), child), HPX_MOVE(iota_shape), HPX_FORWARD(decltype(f), f)); } @@ -372,31 +378,24 @@ namespace hpx::execution::experimental { void start() & noexcept { - hpx::detail::try_catch_exception_ptr( - [&]() { #if defined(HPX_CLANG_VERSION) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif + hpx::detail::try_catch_exception_ptr( + [&]() { scheduler.execute([this]() mutable { hpx::execution::experimental::set_value( HPX_MOVE(receiver)); }); -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic pop -#endif }, [&](std::exception_ptr ep) { -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif hpx::execution::experimental::set_error( HPX_MOVE(receiver), HPX_MOVE(ep)); + }); #if defined(HPX_CLANG_VERSION) #pragma clang diagnostic pop #endif - }); } }; @@ -450,23 +449,6 @@ namespace hpx::execution::experimental { { return sched; } - template - requires(meta::value< - meta::one_of>) - friend constexpr auto tag_invoke( - hpx::execution::experimental::get_completion_scheduler_t< - CPO>, - env const& e) noexcept - { - return e.sched; - } - - friend constexpr auto tag_invoke( - stdexec::get_domain_t, env const& e) noexcept - { - return e.sched.query( - hpx::execution::experimental::get_domain_t{}); - } // P3826R5: get_completion_domain queries // The completing domain is resolved via: @@ -474,9 +456,13 @@ namespace hpx::execution::experimental { // -> scheduler -> get_completion_domain // -> thread_pool_domain template - auto query(stdexec::get_completion_domain_t) const noexcept + auto query( + hpx::execution::experimental::get_completion_domain_t) + const noexcept { - return sched.query(stdexec::get_completion_domain_t{}); + return sched.query( + hpx::execution::experimental::get_completion_domain_t< + CPO>{}); } }; @@ -484,25 +470,14 @@ namespace hpx::execution::experimental { { return env{scheduler}; } - - template - requires( - meta::value>) - friend constexpr auto tag_invoke( - hpx::execution::experimental::get_completion_scheduler_t, - sender const& s) noexcept - { - return s.scheduler; - } }; - friend constexpr hpx::execution::experimental:: - forward_progress_guarantee - tag_invoke( - hpx::execution::experimental::get_forward_progress_guarantee_t, - thread_pool_policy_scheduler const& sched) noexcept + auto query( + hpx::execution::experimental::get_forward_progress_guarantee_t) + const noexcept + -> hpx::execution::experimental::forward_progress_guarantee { - if (hpx::has_async_policy(sched.policy())) + if (hpx::has_async_policy(policy())) { return hpx::execution::experimental:: forward_progress_guarantee::parallel; @@ -520,20 +495,6 @@ namespace hpx::execution::experimental { return {*this}; } - friend constexpr sender tag_invoke( - hpx::execution::experimental::schedule_t, - thread_pool_policy_scheduler&& sched) - { - return {HPX_MOVE(sched)}; - } - - friend constexpr sender tag_invoke( - hpx::execution::experimental::schedule_t, - thread_pool_policy_scheduler const& sched) - { - return {sched}; - } - void policy(Policy policy) noexcept { policy_ = HPX_MOVE(policy); @@ -544,20 +505,22 @@ namespace hpx::execution::experimental { return policy_; } - /// Returns the execution domain of this scheduler (following system_context.hpp pattern). + // Returns the execution domain of this scheduler (following + // system_context.hpp pattern). [[nodiscard]] - auto query(hpx::execution::experimental::get_domain_t) const noexcept + static auto query(hpx::execution::experimental::get_domain_t) noexcept -> thread_pool_domain { return {}; } - /// P3826R5: Returns the completion domain for this scheduler. - /// The domain resolution chain uses this to determine which domains - /// transform_sender to invoke for bulk operations. + // P3826R5: Returns the completion domain for this scheduler. The domain + // resolution chain uses this to determine which domains + // transform_sender to invoke for bulk operations. template [[nodiscard]] - auto query(stdexec::get_completion_domain_t) const noexcept + static auto query( + hpx::execution::experimental::get_completion_domain_t) noexcept -> thread_pool_domain { return {}; @@ -646,33 +609,6 @@ namespace hpx::execution::experimental { HPX_CXX_CORE_EXPORT using thread_pool_scheduler = thread_pool_policy_scheduler; - // Add get_domain query to the scheduler (following system_context.hpp pattern) - template - constexpr auto tag_invoke(hpx::execution::experimental::get_domain_t, - thread_pool_policy_scheduler const&) noexcept - { - return thread_pool_domain{}; - } - - // Add stdexec-specific schedule customization - // stdexec uses its own schedule tag type, so we need to provide tag_invoke for it - template - constexpr auto tag_invoke(hpx::execution::experimental::schedule_t, - thread_pool_policy_scheduler const& sched) noexcept - { - // Return the same sender type as HPX's schedule - return typename thread_pool_policy_scheduler::template sender< - thread_pool_policy_scheduler>{sched}; - } - - template - constexpr auto tag_invoke(hpx::execution::experimental::schedule_t, - thread_pool_policy_scheduler&& sched) noexcept - { - return typename thread_pool_policy_scheduler::template sender< - thread_pool_policy_scheduler>{HPX_MOVE(sched)}; - } - } // namespace hpx::execution::experimental // Include the full bulk sender definition after the scheduler is fully defined diff --git a/libs/core/executors/include/hpx/executors/thread_pool_scheduler_bulk.hpp b/libs/core/executors/include/hpx/executors/thread_pool_scheduler_bulk.hpp index f0e0b6c88e48..4cef61960d2c 100644 --- a/libs/core/executors/include/hpx/executors/thread_pool_scheduler_bulk.hpp +++ b/libs/core/executors/include/hpx/executors/thread_pool_scheduler_bulk.hpp @@ -8,32 +8,17 @@ #pragma once #include -#include - #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include #include @@ -363,22 +348,17 @@ namespace hpx::execution::experimental::detail { using receiver_concept = hpx::execution::experimental::receiver_t; OperationState* op_state; - template - requires std::same_as, bulk_receiver> - friend void tag_invoke(hpx::execution::experimental::set_error_t, - Receiver&& r, E&& e) noexcept + template + void set_error(E&& e) && noexcept { hpx::execution::experimental::set_error( - HPX_MOVE(r.op_state->receiver), HPX_FORWARD(E, e)); + HPX_MOVE(op_state->receiver), HPX_FORWARD(E, e)); } - template - requires std::same_as, bulk_receiver> - friend void tag_invoke( - hpx::execution::experimental::set_stopped_t, Receiver&& r) noexcept + void set_stopped() && noexcept { hpx::execution::experimental::set_stopped( - HPX_MOVE(r.op_state->receiver)); + HPX_MOVE(op_state->receiver)); } // Initialize a queue for a worker thread. @@ -630,16 +610,14 @@ namespace hpx::execution::experimental::detail { } } - template - requires std::same_as, bulk_receiver> - friend void tag_invoke(hpx::execution::experimental::set_value_t, - Receiver&& r, Ts&&... ts) noexcept + template + void set_value(Ts&&... ts) && noexcept { hpx::detail::try_catch_exception_ptr( - [&]() { r.execute(HPX_FORWARD(Ts, ts)...); }, + [&]() { this->execute(HPX_FORWARD(Ts, ts)...); }, [&](std::exception_ptr ep) { hpx::execution::experimental::set_error( - HPX_MOVE(r.op_state->receiver), HPX_MOVE(ep)); + HPX_MOVE(op_state->receiver), HPX_MOVE(ep)); }); } }; @@ -707,22 +685,24 @@ namespace hpx::execution::experimental::detail { using sender_concept = hpx::execution::experimental::sender_t; - template -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif - friend auto tag_invoke( - hpx::execution::experimental::get_completion_signatures_t, - thread_pool_bulk_sender const&, Env const&) - -> hpx::execution::experimental::transform_completion_signatures_of< - Sender, Env, - hpx::execution::experimental::completion_signatures< - hpx::execution::experimental::set_error_t( - std::exception_ptr)>>; -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic pop -#endif + template + static consteval auto get_completion_signatures() noexcept + -> decltype(hpx::execution::experimental:: + transform_completion_signatures( + hpx::execution::experimental:: + completion_signatures_of_t{}, + hpx::execution::experimental::keep_completion< + hpx::execution::experimental::set_value_t>{}, + hpx::execution::experimental::keep_completion< + hpx::execution::experimental::set_error_t>{}, + hpx::execution::experimental::keep_completion< + hpx::execution::experimental::set_stopped_t>{}, + hpx::execution::experimental::completion_signatures< + hpx::execution::experimental::set_error_t( + std::exception_ptr)>{})) + { + return {}; + } struct env { @@ -755,19 +735,21 @@ namespace hpx::execution::experimental::detail { // P3826R5: report the completion domain for this bulk sender template - auto query(stdexec::get_completion_domain_t) const noexcept + auto query( + hpx::execution::experimental::get_completion_domain_t) + const noexcept { - return sch.query(stdexec::get_completion_domain_t{}); + return sch.query( + hpx::execution::experimental::get_completion_domain_t< + CPO>{}); } }; // It may be also be correct to forward the entire env of the // pred. sender. - friend constexpr auto tag_invoke( - hpx::execution::experimental::get_env_t, - thread_pool_bulk_sender const& s) noexcept + constexpr auto get_env() const noexcept { - return env{s.sender, s.scheduler}; + return env{sender, scheduler}; } private: @@ -827,9 +809,9 @@ namespace hpx::execution::experimental::detail { HPX_ASSERT(hpx::threads::count(pu_mask) == num_worker_threads); } - friend void tag_invoke(start_t, operation_state& os) noexcept + void start() & noexcept { - hpx::execution::experimental::start(os.op_state); + hpx::execution::experimental::start(op_state); } }; diff --git a/libs/core/executors/src/fork_join_executor.cpp b/libs/core/executors/src/fork_join_executor.cpp index 7392293d8a3c..a69c453ef185 100644 --- a/libs/core/executors/src/fork_join_executor.cpp +++ b/libs/core/executors/src/fork_join_executor.cpp @@ -6,7 +6,9 @@ #include +#include #include +#include namespace hpx::execution::experimental { @@ -27,9 +29,9 @@ namespace hpx::execution::experimental { } os << " (" - << static_cast< + << static_cast(static_cast< std::underlying_type_t>( - schedule) + schedule)) << ")"; return os; diff --git a/libs/core/executors/tests/regressions/CMakeLists.txt b/libs/core/executors/tests/regressions/CMakeLists.txt index d9676fbfddd3..36c576b51898 100644 --- a/libs/core/executors/tests/regressions/CMakeLists.txt +++ b/libs/core/executors/tests/regressions/CMakeLists.txt @@ -15,10 +15,6 @@ set(tests wrapping_executor ) -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - list(REMOVE_ITEM tests bulk_sync_wait) -endif() - foreach(test ${tests}) set(sources ${test}.cpp) diff --git a/libs/core/executors/tests/regressions/bulk_sync_wait.cpp b/libs/core/executors/tests/regressions/bulk_sync_wait.cpp index 73b227cc7c8c..434b0cb8b2a6 100644 --- a/libs/core/executors/tests/regressions/bulk_sync_wait.cpp +++ b/libs/core/executors/tests/regressions/bulk_sync_wait.cpp @@ -24,7 +24,7 @@ int hpx_main() auto s = ex::starts_on( sch, ex::just() | ex::bulk(1, [&called](auto) { called = true; })); - tt::sync_wait(s); + tt::sync_wait(HPX_MOVE(s)); HPX_TEST(called.load()); diff --git a/libs/core/executors/tests/unit/CMakeLists.txt b/libs/core/executors/tests/unit/CMakeLists.txt index e11e726808c1..cfc469a07049 100644 --- a/libs/core/executors/tests/unit/CMakeLists.txt +++ b/libs/core/executors/tests/unit/CMakeLists.txt @@ -34,26 +34,35 @@ if(HPX_LIKWID_WITH_LIKWID) set(tests ${tests} likwid_executor) endif() -if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - foreach(test ${tests}) - set(sources ${test}.cpp) +foreach(test ${tests}) + set(sources ${test}.cpp) - set(${test}_PARAMETERS THREADS_PER_LOCALITY 4) + set(${test}_PARAMETERS THREADS_PER_LOCALITY 4) - source_group("Source Files" FILES ${sources}) + source_group("Source Files" FILES ${sources}) - set(folder_name "Tests/Unit/Modules/Core/Executors") + set(folder_name "Tests/Unit/Modules/Core/Executors") - # add example executable - add_hpx_executable( - ${test}_test INTERNAL_FLAGS - SOURCES ${sources} ${${test}_FLAGS} - EXCLUDE_FROM_ALL - HPX_PREFIX ${HPX_BUILD_PREFIX} - FOLDER ${folder_name} - ) + # add example executable + add_hpx_executable( + ${test}_test INTERNAL_FLAGS + SOURCES ${sources} ${${test}_FLAGS} + EXCLUDE_FROM_ALL + HPX_PREFIX ${HPX_BUILD_PREFIX} + FOLDER ${folder_name} + ) + + add_hpx_unit_test("modules.executors" ${test} ${${test}_PARAMETERS}) - add_hpx_unit_test("modules.executors" ${test} ${${test}_PARAMETERS}) +endforeach() +if(HPX_WITH_CXX_MODULES AND (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) + # Clang (last tested version is v22) fails compiling the following tests when + # C++ module support is enabled. + set(failing_clang_tests explicit_scheduler_executor thread_pool_scheduler) + foreach(test ${failing_clang_tests}) + target_compile_definitions( + ${test}_test PRIVATE HPX_HAVE_FORCE_NO_CXX_MODULES + ) endforeach() endif() diff --git a/libs/core/executors/tests/unit/thread_pool_scheduler.cpp b/libs/core/executors/tests/unit/thread_pool_scheduler.cpp index ed629e421a9d..12c6b098669d 100644 --- a/libs/core/executors/tests/unit/thread_pool_scheduler.cpp +++ b/libs/core/executors/tests/unit/thread_pool_scheduler.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2020 ETH Zurich -// Copyright (c) 2022-2025 Hartmut Kaiser +// Copyright (c) 2022-2026 Hartmut Kaiser // // SPDX-License-Identifier: BSL-1.0 // Distributed under the Boost Software License, Version 1.0. (See accompanying @@ -7,14 +7,14 @@ #include -// Clang V11 ICE's on this test, Clang V8 reports a bogus constexpr problem -#if !defined(HPX_CLANG_VERSION) || \ - ((HPX_CLANG_VERSION / 10000) != 11 && (HPX_CLANG_VERSION / 10000) != 8) +// Clang fails compiling this test if the version is less than 22 +#if !defined(HPX_CLANG_VERSION) || ((HPX_CLANG_VERSION / 10000) > 22) #include #include #include #include +#include #include #include #include @@ -72,8 +72,9 @@ void test_execute() hpx::thread::id parent_id = hpx::this_thread::get_id(); ex::thread_pool_scheduler sched{}; - ex::execute(sched, - [parent_id]() { HPX_TEST_NEQ(hpx::this_thread::get_id(), parent_id); }); + ex::start_detached(ex::schedule(sched) | ex::then([parent_id]() { + HPX_TEST_NEQ(hpx::this_thread::get_id(), parent_id); + })); } struct check_context_receiver @@ -84,27 +85,25 @@ struct check_context_receiver bool& executed; using receiver_concept = ex::receiver_t; template - friend void tag_invoke( - ex::set_error_t, check_context_receiver&&, E&&) noexcept + void set_error(E&&) && noexcept { HPX_TEST(false); } - friend void tag_invoke(ex::set_stopped_t, check_context_receiver&&) noexcept + void set_stopped() && noexcept { HPX_TEST(false); } template - friend void tag_invoke( - ex::set_value_t, check_context_receiver&& r, Ts&&...) noexcept + void set_value(Ts&&...) && noexcept { - HPX_TEST_NEQ(r.parent_id, hpx::this_thread::get_id()); + HPX_TEST_NEQ(parent_id, hpx::this_thread::get_id()); HPX_TEST_NEQ(hpx::thread::id(hpx::threads::invalid_thread_id), hpx::this_thread::get_id()); - std::lock_guard l{r.mtx}; - r.executed = true; - r.cond.notify_one(); + std::lock_guard l{mtx}; + executed = true; + cond.notify_one(); } }; @@ -249,24 +248,23 @@ struct callback_receiver using receiver_concept = ex::receiver_t; template - friend void tag_invoke(ex::set_error_t, callback_receiver&&, E&&) noexcept + void set_error(E&&) && noexcept { HPX_TEST(false); } - friend void tag_invoke(ex::set_stopped_t, callback_receiver&&) noexcept + void set_stopped() && noexcept { HPX_TEST(false); } template - friend void tag_invoke( - ex::set_value_t, callback_receiver&& r, Ts&&...) noexcept + void set_value(Ts&&...) && noexcept { - HPX_INVOKE(r.f, ); - std::lock_guard l{r.mtx}; - r.executed = true; - r.cond.notify_one(); + HPX_INVOKE(f, ); + std::lock_guard l{mtx}; + executed = true; + cond.notify_one(); } }; @@ -553,8 +551,8 @@ void test_bulk_starts_on() hpx::thread::id parent_id = hpx::this_thread::get_id(); // Test starts_on pattern: bulk operation with scheduler in environment - // Use start_on to provide scheduler through environment - auto bulk_sender = ex::continues_on( + // Use starts_on to schedule bulk on the thread pool + auto bulk_sender = ex::starts_on( ex::thread_pool_scheduler{}, ex::just() | ex::bulk(n, [&](int i) { ++v[i]; HPX_TEST_NEQ(parent_id, hpx::this_thread::get_id()); @@ -865,7 +863,7 @@ void test_future_sender() } { - auto s = ex::just(ex::thread_pool_scheduler{}, 3); + auto s = ex::starts_on(ex::thread_pool_scheduler{}, ex::just(3)); auto f = ex::make_future(std::move(s)); HPX_TEST_EQ(f.get(), 3); } @@ -876,7 +874,8 @@ void test_future_sender() } { - auto f = ex::just(ex::thread_pool_scheduler{}, 3) | ex::make_future(); + auto f = ex::starts_on(ex::thread_pool_scheduler{}, ex::just(3)) | + ex::make_future(); HPX_TEST_EQ(f.get(), 3); } @@ -890,9 +889,11 @@ void test_future_sender() } { - auto s1 = ex::just(ex::thread_pool_scheduler{}, std::size_t(42)); - auto s2 = ex::just(ex::thread_pool_scheduler{}, 3.14); - auto s3 = ex::just(ex::thread_pool_scheduler{}, std::string("hello")); + auto s1 = ex::starts_on( + ex::thread_pool_scheduler{}, ex::just(std::size_t(42))); + auto s2 = ex::starts_on(ex::thread_pool_scheduler{}, ex::just(3.14)); + auto s3 = ex::starts_on( + ex::thread_pool_scheduler{}, ex::just(std::string("hello"))); auto f = ex::make_future(ex::then( ex::when_all(std::move(s1), std::move(s2), std::move(s3)), [](std::size_t x, double, std::string z) { return z.size() + x; })); @@ -901,8 +902,9 @@ void test_future_sender() // mixing senders and futures { - HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(ex::as_sender(ex::make_future( - ex::just(ex::thread_pool_scheduler{}, 42))))), + HPX_TEST_EQ( + hpx::get<0>(*tt::sync_wait(ex::as_sender(ex::make_future( + ex::starts_on(ex::thread_pool_scheduler{}, ex::just(42)))))), 42); } @@ -916,9 +918,11 @@ void test_future_sender() } { - auto s1 = ex::just(ex::thread_pool_scheduler{}, std::size_t(42)); - auto s2 = ex::just(ex::thread_pool_scheduler{}, 3.14); - auto s3 = ex::just(ex::thread_pool_scheduler{}, std::string("hello")); + auto s1 = ex::starts_on( + ex::thread_pool_scheduler{}, ex::just(std::size_t(42))); + auto s2 = ex::starts_on(ex::thread_pool_scheduler{}, ex::just(3.14)); + auto s3 = ex::starts_on( + ex::thread_pool_scheduler{}, ex::just(std::string("hello"))); auto f = ex::make_future(ex::then( ex::when_all(std::move(s1), std::move(s2), std::move(s3)), [](std::size_t x, double, std::string z) { return z.size() + x; })); @@ -945,18 +949,19 @@ void test_ensure_started() } { - auto s = ex::just(sched, 42) | ex::ensure_started(); + auto s = ex::starts_on(sched, ex::just(42)) | ex::ensure_started(); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(std::move(s))), 42); } { - auto s = ex::just(sched, 42) | ex::ensure_started() | + auto s = ex::starts_on(sched, ex::just(42)) | ex::ensure_started() | ex::continues_on(sched); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(std::move(s))), 42); } { - auto s = ex::just(sched, 42) | ex::ensure_started() | ex::split(); + auto s = ex::starts_on(sched, ex::just(42)) | ex::ensure_started() | + ex::split(); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); @@ -1081,17 +1086,18 @@ void test_split() } { - auto s = ex::just(sched, 42) | ex::split(); + auto s = ex::starts_on(sched, ex::just(42)) | ex::split(); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(std::move(s))), 42); } { - auto s = ex::just(sched, 42) | ex::split() | ex::continues_on(sched); + auto s = ex::starts_on(sched, ex::just(42)) | ex::split() | + ex::continues_on(sched); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(std::move(s))), 42); } { - auto s = ex::just(sched, 42) | ex::split(); + auto s = ex::starts_on(sched, ex::just(42)) | ex::split(); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); HPX_TEST_EQ(hpx::get<0>(*tt::sync_wait(s)), 42); @@ -1183,40 +1189,49 @@ void test_let_value() } { - auto result = hpx::get<0>(*(tt::sync_wait(ex::schedule(sched) | - ex::let_value([=]() { return ex::just(sched, 42); })))); + auto result = hpx::get<0>( + *(tt::sync_wait(ex::schedule(sched) | ex::let_value([=]() { + return ex::starts_on(sched, ex::just(42)); + })))); HPX_TEST_EQ(result, 42); } { - auto result = hpx::get<0>(*tt::sync_wait((ex::just() | - ex::let_value([=]() { return ex::just(sched, 42); })))); + auto result = + hpx::get<0>(*tt::sync_wait((ex::just() | ex::let_value([=]() { + return ex::starts_on(sched, ex::just(42)); + })))); HPX_TEST_EQ(result, 42); } // int predecessor, value ignored { - auto result = hpx::get<0>(*(tt::sync_wait(ex::just(sched, 43) | - ex::let_value([](int&) { return ex::just(42); })))); + auto result = + hpx::get<0>(*(tt::sync_wait(ex::starts_on(sched, ex::just(43)) | + ex::let_value([](int&) { return ex::just(42); })))); HPX_TEST_EQ(result, 42); } { - auto result = hpx::get<0>(*(tt::sync_wait(ex::just(sched, 43) | - ex::let_value([=](int&) { return ex::just(sched, 42); })))); + auto result = hpx::get<0>(*(tt::sync_wait( + ex::starts_on(sched, ex::just(43)) | ex::let_value([=](int&) { + return ex::starts_on(sched, ex::just(42)); + })))); HPX_TEST_EQ(result, 42); } { - auto result = hpx::get<0>(*(tt::sync_wait(ex::just(43) | - ex::let_value([=](int&) { return ex::just(sched, 42); })))); + auto result = + hpx::get<0>(*(tt::sync_wait(ex::just(43) | ex::let_value([=](int&) { + return ex::starts_on(sched, ex::just(42)); + })))); HPX_TEST_EQ(result, 42); } // int predecessor, value used { - auto result = hpx::get<0>( - *(tt::sync_wait(ex::just(sched, 43) | ex::let_value([](int& x) { + auto result = hpx::get<0>(*(tt::sync_wait( + ex::starts_on(sched, ex::just(43)) | ex::let_value([](int& x) { return ex::just(42) | ex::then([&](int y) { return x + y; }); })))); @@ -1224,9 +1239,9 @@ void test_let_value() } { - auto result = hpx::get<0>( - *(tt::sync_wait(ex::just(sched, 43) | ex::let_value([=](int& x) { - return ex::just(sched, 42) | + auto result = hpx::get<0>(*(tt::sync_wait( + ex::starts_on(sched, ex::just(43)) | ex::let_value([=](int& x) { + return ex::starts_on(sched, ex::just(42)) | ex::then([&](int y) { return x + y; }); })))); HPX_TEST_EQ(result, 85); @@ -1235,7 +1250,7 @@ void test_let_value() { auto result = hpx::get<0>( *(tt::sync_wait(ex::just(43) | ex::let_value([=](int& x) { - return ex::just(sched, 42) | + return ex::starts_on(sched, ex::just(42)) | ex::then([&](int y) { return x + y; }); })))); HPX_TEST_EQ(result, 85); @@ -1247,13 +1262,15 @@ void test_let_value() try { - tt::sync_wait(ex::just(sched, 43) | ex::then([](int x) { - throw std::runtime_error("error"); - return x; - }) | ex::let_value([](int&) { - HPX_TEST(false); - return ex::just(0); - })); + tt::sync_wait(ex::starts_on(sched, ex::just(43)) | + ex::then([](int x) { + throw std::runtime_error("error"); + return x; + }) | + ex::let_value([](int&) { + HPX_TEST(false); + return ex::just(0); + })); HPX_TEST(false); } catch (std::runtime_error const& e) @@ -1306,7 +1323,7 @@ void test_let_error() }) | ex::let_error([=, &called](std::exception_ptr& ep) { called = true; check_exception_ptr_message(ep, "error"); - return ex::just(sched); + return ex::just(); })); HPX_TEST(called); } @@ -1318,7 +1335,7 @@ void test_let_error() }) | ex::let_error([=, &called](std::exception_ptr& ep) { called = true; check_exception_ptr_message(ep, "error"); - return ex::just(sched); + return ex::just(); })); HPX_TEST(called); } @@ -1343,7 +1360,7 @@ void test_let_error() return 43; }) | ex::let_error([=](std::exception_ptr& ep) { check_exception_ptr_message(ep, "error"); - return ex::just(sched, 42); + return ex::starts_on(sched, ex::just(42)); })))); HPX_TEST_EQ(result, 42); } @@ -1354,27 +1371,29 @@ void test_let_error() return 43; }) | ex::let_error([=](std::exception_ptr& ep) { check_exception_ptr_message(ep, "error"); - return ex::just(sched, 42); + return ex::starts_on(sched, ex::just(42)); })))); HPX_TEST_EQ(result, 42); } // predecessor doesn't throw, let sender is ignored { - auto result = hpx::get<0>(*(tt::sync_wait( - ex::just(sched, 42) | ex::let_error([](std::exception_ptr) { - HPX_TEST(false); - return ex::just(43); - })))); + auto result = + hpx::get<0>(*(tt::sync_wait(ex::starts_on(sched, ex::just(42)) | + ex::let_error([](std::exception_ptr) { + HPX_TEST(false); + return ex::just(43); + })))); HPX_TEST_EQ(result, 42); } { - auto result = hpx::get<0>(*(tt::sync_wait( - ex::just(sched, 42) | ex::let_error([=](std::exception_ptr) { - HPX_TEST(false); - return ex::just(sched, 43); - })))); + auto result = + hpx::get<0>(*(tt::sync_wait(ex::starts_on(sched, ex::just(42)) | + ex::let_error([=](std::exception_ptr) { + HPX_TEST(false); + return ex::starts_on(sched, ex::just(43)); + })))); HPX_TEST_EQ(result, 42); } @@ -1382,7 +1401,7 @@ void test_let_error() auto result = hpx::get<0>(*( tt::sync_wait(ex::just(42) | ex::let_error([=](std::exception_ptr) { HPX_TEST(false); - return ex::just(sched, 43); + return ex::starts_on(sched, ex::just(43)); })))); HPX_TEST_EQ(result, 42); } @@ -1683,12 +1702,12 @@ void test_bulk() std::vector v(n, -1); hpx::thread::id parent_id = hpx::this_thread::get_id(); - auto v_out = hpx::get<0>(*( - tt::sync_wait(ex::just(ex::thread_pool_scheduler{}, std::move(v)) | - ex::bulk(n, [&parent_id](int i, std::vector& v) { - v[i] = i; - HPX_TEST_NEQ(parent_id, hpx::this_thread::get_id()); - })))); + auto v_out = hpx::get<0>(*(tt::sync_wait( + ex::starts_on(ex::thread_pool_scheduler{}, ex::just(std::move(v))) | + ex::bulk(n, [&parent_id](int i, std::vector& v) { + v[i] = i; + HPX_TEST_NEQ(parent_id, hpx::this_thread::get_id()); + })))); // In chunked mode, only chunk begin indices are processed // So we check that at least some elements were set correctly @@ -1710,65 +1729,50 @@ void test_bulk() } { - std::unordered_set string_map; - std::vector v = {"hello", "brave", "new", "world"}; - std::vector v_ref = v; - - hpx::mutex mtx; - tt::sync_wait(ex::schedule(ex::thread_pool_scheduler{}) | - ex::bulk(std::move(v), [&](std::string const& s) { - std::lock_guard lk(mtx); - string_map.insert(s); - })); - - for (auto const& s : v_ref) + for (auto n : ns) { - HPX_TEST(string_map.find(s) != string_map.end()); - } - } - - for (auto n : ns) - { - int i_fail = 3; - - std::vector v(n, -1); - bool const expect_exception = n > i_fail; + int i_fail = 3; - try - { - tt::sync_wait(ex::just(ex::thread_pool_scheduler{}) | - ex::bulk(n, [&v, i_fail](int i) { - if (i == i_fail) - { - throw std::runtime_error("error"); - } - v[i] = i; - })); + std::vector v(n, -1); + bool const expect_exception = n > i_fail; - if (expect_exception) + try { - HPX_TEST(false); + tt::sync_wait( + ex::starts_on(ex::thread_pool_scheduler{}, ex::just()) | + ex::bulk(n, [&v, i_fail](int i) { + if (i == i_fail) + { + throw std::runtime_error("error"); + } + v[i] = i; + })); + + if (expect_exception) + { + HPX_TEST(false); + } } - } - catch (std::runtime_error const& e) - { - if (!expect_exception) + catch (std::runtime_error const& e) { - HPX_TEST(false); - } + if (!expect_exception) + { + HPX_TEST(false); + } - HPX_TEST(std::string(e.what()).find("error") == 0); - } + HPX_TEST(std::string(e.what()).find("error") == 0); + } - if (expect_exception) - { - HPX_TEST_EQ(v[i_fail], -1); - } - else - { - for (int i = 0; i < n; ++i) + if (expect_exception) { - HPX_TEST_EQ(v[i], i); + HPX_TEST_EQ(v[i_fail], -1); + } + else + { + for (int i = 0; i < n; ++i) + { + HPX_TEST_EQ(v[i], i); + } } } } @@ -1804,7 +1808,7 @@ void test_stdexec_domain_queries() // 4. Verify transform_sender produces thread_pool_bulk_sender for // bulk_chunked (proves the domain customization is picked up) { - auto env = ex::env{ex::prop{ex::get_scheduler, scheduler}}; + auto env = ex::make_env(ex::prop(ex::get_scheduler, scheduler)); auto chunked_sndr = ex::bulk_chunked( ex::schedule(scheduler), ex::par, 10, [](int, int) {}); @@ -1827,7 +1831,7 @@ void test_stdexec_domain_queries() // 5. Verify transform_sender produces thread_pool_bulk_sender for // bulk_unchunked (proves the domain customization is picked up) { - auto env = ex::env{ex::prop{ex::get_scheduler, scheduler}}; + auto env = ex::make_env(ex::prop(ex::get_scheduler, scheduler)); auto unchunked_sndr = ex::bulk_unchunked( ex::schedule(scheduler), ex::par, 10, [](int) {}); @@ -1975,16 +1979,16 @@ void test_stdexec_bulk_unchunked_customization() void test_stdexec_thread_distribution() { auto scheduler = ex::thread_pool_scheduler{}; - std::thread::id main_thread_id = std::this_thread::get_id(); + hpx::thread::id main_id = hpx::this_thread::get_id(); // Test that bulk operations run on worker threads - std::set worker_threads; + std::set worker_threads; std::atomic task_count{0}; auto bulk_sender = ex::bulk_chunked(ex::schedule(scheduler) | ex::then([]() { return 0; }), ex::par, 8, [&](int start, int end, int value) { - worker_threads.insert(std::this_thread::get_id()); + worker_threads.insert(hpx::this_thread::get_id()); for (int idx = start; idx < end; ++idx) { (void) value; @@ -2000,10 +2004,10 @@ void test_stdexec_thread_distribution() HPX_TEST(task_count.load() > 0); // Should have at least 1 call HPX_TEST(!worker_threads.empty()); - // Verify tasks didn't run on main thread (they use HPX thread pool) + // Verify bulk work ran on different HPX threads than the caller for (auto const& thread_id : worker_threads) { - HPX_TEST_NEQ(thread_id, main_thread_id); + HPX_TEST_NEQ(thread_id, main_id); } } @@ -2090,7 +2094,8 @@ void test_completion_scheduler() } { - auto sender = ex::just(ex::thread_pool_scheduler{}, 42); + auto sender = + ex::continues_on(ex::just(42), ex::thread_pool_scheduler{}); auto completion_scheduler = ex::get_completion_scheduler(ex::get_env(sender)); static_assert( @@ -2112,8 +2117,8 @@ void test_completion_scheduler() { auto sender = ex::then( - ex::bulk(ex::just(ex::thread_pool_scheduler{}, 42), 10, - [](int, int) {}), + ex::bulk(ex::continues_on(ex::just(42), ex::thread_pool_scheduler{}), + 10, [](int, int) {}), [](int) {}); auto completion_scheduler = ex::get_completion_scheduler(ex::get_env(sender)); @@ -2136,7 +2141,7 @@ void test_completion_scheduler() { auto sender = ex::then( - ex::bulk(ex::just(ex::thread_pool_scheduler{}, 42), + ex::bulk(ex::continues_on(ex::just(42), ex::thread_pool_scheduler{}), ex::par, 10, [](int, int) {}), [](int) {}); auto completion_scheduler = @@ -2149,7 +2154,7 @@ void test_completion_scheduler() { auto sender = ex::bulk( - ex::then(ex::just(ex::thread_pool_scheduler{}, 42), + ex::then(ex::continues_on(ex::just(42), ex::thread_pool_scheduler{}), [](int i) { return i; }), ex::par, 10, [](int idx, int val) {}); auto completion_scheduler = @@ -2320,10 +2325,6 @@ int main(int argc, char* argv[]) return hpx::util::report_errors(); } - -#if defined(HPX_CLANG_VERSION) -#pragma clang diagnostic pop -#endif #else int main() { diff --git a/libs/core/iterator_support/include/hpx/iterator_support/iterator_range.hpp b/libs/core/iterator_support/include/hpx/iterator_support/iterator_range.hpp index 1434d999bb2a..68a381f554a4 100644 --- a/libs/core/iterator_support/include/hpx/iterator_support/iterator_range.hpp +++ b/libs/core/iterator_support/include/hpx/iterator_support/iterator_range.hpp @@ -61,7 +61,7 @@ namespace hpx::util { [[nodiscard]] HPX_HOST_DEVICE constexpr std::ptrdiff_t size() const { - return std::distance(_iterator, _sentinel); + return std::ranges::distance(_iterator, _sentinel); } [[nodiscard]] HPX_HOST_DEVICE constexpr bool empty() const @@ -127,5 +127,4 @@ namespace hpx::ranges { HPX_CXX_CORE_EXPORT template using subrange_t = hpx::util::iterator_range; -} -// namespace hpx::ranges +} // namespace hpx::ranges diff --git a/libs/core/synchronization/CMakeLists.txt b/libs/core/synchronization/CMakeLists.txt index cffec573e779..7555efe16667 100644 --- a/libs/core/synchronization/CMakeLists.txt +++ b/libs/core/synchronization/CMakeLists.txt @@ -8,7 +8,6 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") # Default location is $HPX_ROOT/libs/synchronization/include set(synchronization_headers - hpx/synchronization/async_rw_mutex.hpp hpx/synchronization/barrier.hpp hpx/synchronization/binary_semaphore.hpp hpx/synchronization/channel_mpmc.hpp diff --git a/libs/core/synchronization/tests/unit/CMakeLists.txt b/libs/core/synchronization/tests/unit/CMakeLists.txt index 73b80a4b8afe..57964bc155c9 100644 --- a/libs/core/synchronization/tests/unit/CMakeLists.txt +++ b/libs/core/synchronization/tests/unit/CMakeLists.txt @@ -58,10 +58,6 @@ set(stop_token_PARAMETERS THREADS_PER_LOCALITY 4) set(in_place_stop_token_cb2_PARAMETERS THREADS_PER_LOCALITY 4) set(in_place_stop_token_PARAMETERS THREADS_PER_LOCALITY 4) -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - list(REMOVE_ITEM tests async_rw_mutex) -endif() - foreach(test ${tests}) set(sources ${test}.cpp) diff --git a/libs/core/synchronization/tests/unit/async_rw_mutex.cpp b/libs/core/synchronization/tests/unit/async_rw_mutex.cpp index 3101ba316f49..2f8c74d5bbf6 100644 --- a/libs/core/synchronization/tests/unit/async_rw_mutex.cpp +++ b/libs/core/synchronization/tests/unit/async_rw_mutex.cpp @@ -18,7 +18,7 @@ #include using hpx::execution::experimental::continues_on; -using hpx::execution::experimental::execute; +using hpx::execution::experimental::start_detached; using hpx::execution::experimental::then; using hpx::execution::experimental::thread_pool_scheduler; using hpx::experimental::async_rw_mutex; @@ -153,20 +153,6 @@ struct checker } }; -template -void submit_senders(Executor&& exec, Senders& senders) -{ - for (auto& sender : senders) - { - // Original code uses sync_wait inside an hpx scheduler. Sync_wait completely - // blocks the thread with std synchronization primitives which causes it to hang - hpx::execution::experimental::start_detached( - hpx::execution::experimental::schedule(exec) | - hpx::execution::experimental::let_value( - [s = std::move(sender)]() mutable { return std::move(s); })); - } -} - template void test_single_read_access(async_rw_mutex rwm) { @@ -250,11 +236,20 @@ void test_multiple_accesses( sender_helper(false); } - // Asynchronously submit the senders - submit_senders(exec, r_senders); - submit_senders(exec, rw_senders); + // Submit the senders using start_detached (fire-and-forget, doesn't block + // HPX threads) + for (auto& sender : r_senders) + { + start_detached(std::move(sender)); + } + + for (auto& sender : rw_senders) + { + start_detached(std::move(sender)); + } - // The destructor does not block, so we block here manually + // Use a final readwrite access as a barrier to wait for all previous + // operations to complete sync_wait(rwm.readwrite()); } diff --git a/libs/core/type_support/include/hpx/type_support/is_replaceable.hpp b/libs/core/type_support/include/hpx/type_support/is_replaceable.hpp index 7af8baf4077f..418f2a710020 100644 --- a/libs/core/type_support/include/hpx/type_support/is_replaceable.hpp +++ b/libs/core/type_support/include/hpx/type_support/is_replaceable.hpp @@ -13,11 +13,15 @@ namespace hpx::experimental { -// P2786R13 specifies a single feature-test macro __cpp_trivial_relocatability -// that signals availability of both the language facilities and the library -// trait std::is_replaceable, see -// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2786r13.html#language-feature-test-macros -#if defined(__cpp_trivial_relocatability) + // P2786R13 specifies a single feature-test macro __cpp_trivial_relocatability + // that signals availability of both the language facilities and the library + // trait std::is_replaceable, see + // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2786r13.html#language-feature-test-macros + + // Note: while Clang V22.0.0 defines __cpp_trivial_relocatability pre C++26, it + // does not have std::is_replaceable. + +#if __cplusplus >= 202611 && defined(__cpp_trivial_relocatability) HPX_CXX_CORE_EXPORT template struct is_replaceable : std::is_replaceable { diff --git a/libs/core/type_support/include/hpx/type_support/is_trivially_relocatable.hpp b/libs/core/type_support/include/hpx/type_support/is_trivially_relocatable.hpp index f2fbdca786ea..4e13d0acabc3 100644 --- a/libs/core/type_support/include/hpx/type_support/is_trivially_relocatable.hpp +++ b/libs/core/type_support/include/hpx/type_support/is_trivially_relocatable.hpp @@ -15,14 +15,17 @@ namespace hpx::experimental { // P2786R13 specifies a single feature-test macro __cpp_trivial_relocatability // that signals availability of both the language facilities and the library // trait std::is_trivially_relocatable, see + +// Note: while Clang V22.0.0 defines __cpp_trivial_relocatability pre C++26, it +// does not have std::is_trivially_relocatable. + // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2786r13.html#language-feature-test-macros -#if defined(__cpp_trivial_relocatability) +#if __cplusplus >= 202611 && defined(__cpp_trivial_relocatability) HPX_CXX_CORE_EXPORT template struct is_trivially_relocatable : std::is_trivially_relocatable { }; #else - // All trivially copyable types are trivially relocatable // Other types should default to false. HPX_CXX_CORE_EXPORT template @@ -84,7 +87,6 @@ namespace hpx::experimental { : is_trivially_relocatable { }; - #endif HPX_CXX_CORE_EXPORT template diff --git a/tests/performance/local/CMakeLists.txt b/tests/performance/local/CMakeLists.txt index c01384ef8d97..f5ac538e28a1 100644 --- a/tests/performance/local/CMakeLists.txt +++ b/tests/performance/local/CMakeLists.txt @@ -36,10 +36,6 @@ if(NOT HPX_WITH_CUDA_COMPUTE) set(stream_FLAGS CUDA) endif() -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") - list(REMOVE_ITEM benchmarks stream_report) -endif() - if(NOT HPX_WITH_SANITIZERS) list(APPEND benchmarks start_stop) endif() diff --git a/tools/inspect/include_check.cpp b/tools/inspect/include_check.cpp index a119a597a16d..d6cd0f7dca2b 100644 --- a/tools/inspect/include_check.cpp +++ b/tools/inspect/include_check.cpp @@ -210,6 +210,8 @@ namespace boost { namespace inspect { {"type_traits"}}, {"(\\bstd\\s*::\\s*underlying_type\\b)", "std::underlying_type", {"type_traits"}}, + {"(\\bstd\\s*::\\s*underlying_type_t\\b)", "std::underlying_type_t", + {"type_traits"}}, {"(\\bstd\\s*::\\s*result_of\\b)", "std::result_of", {"type_traits"}}, // cstring {"(\\bstd\\s*::\\s*(mem((set)|(cpy)|(move)))\\b)", "std::\\2",