From 08cbce708e475bfc24368e76d82c8cc8e8d31b0d Mon Sep 17 00:00:00 2001 From: Hartmut Kaiser Date: Thu, 1 Jan 2026 11:25:09 -0600 Subject: [PATCH] Attempting to improve compiler error messages - this is an experiment of how to best improve generated error messages if no appropriate tag_invoke overloads are being available Signed-off-by: Hartmut Kaiser --- .../hpx/parallel/algorithms/partial_sort.hpp | 17 +- .../include/hpx/parallel/algorithms/sort.hpp | 139 ++++++------- .../hpx/execution/executors/execution.hpp | 61 ++---- .../include/hpx/execution_base/execution.hpp | 186 ++++++++++++++++-- .../hpx/executors/parallel_executor.hpp | 6 +- .../functional/detail/tag_fallback_invoke.hpp | 2 +- .../functional/detail/tag_priority_invoke.hpp | 7 +- 7 files changed, 277 insertions(+), 141 deletions(-) diff --git a/libs/core/algorithms/include/hpx/parallel/algorithms/partial_sort.hpp b/libs/core/algorithms/include/hpx/parallel/algorithms/partial_sort.hpp index e3e217a8d8cb..bad01d47f88b 100644 --- a/libs/core/algorithms/include/hpx/parallel/algorithms/partial_sort.hpp +++ b/libs/core/algorithms/include/hpx/parallel/algorithms/partial_sort.hpp @@ -346,15 +346,6 @@ namespace hpx::parallel { hpx::future parallel_partial_sort(ExPolicy&& policy, Iter first, Iter middle, Iter last, std::uint32_t level, Comp&& comp = Comp()); - HPX_CXX_EXPORT struct sort_thread_helper - { - template - decltype(auto) operator()(Ts&&... ts) const - { - return sort_thread(HPX_FORWARD(Ts, ts)...); - } - }; - HPX_CXX_EXPORT struct parallel_partial_sort_helper { template @@ -399,15 +390,15 @@ namespace hpx::parallel { policy.parameters(), policy.executor(), hpx::chrono::null_duration, cores, nelem); - hpx::future left = execution::async_execute( - policy.executor(), sort_thread_helper(), policy, first, - c_last, comp, chunk_size); + hpx::future left = + execution::async_execute(policy.executor(), sort_thread{}, + policy, first, c_last, comp, chunk_size); hpx::future right; if (middle != c_last) { right = execution::async_execute(policy.executor(), - parallel_partial_sort_helper(), policy, c_last + 1, + parallel_partial_sort_helper{}, policy, c_last + 1, middle, last, level - 1, comp); } else diff --git a/libs/core/algorithms/include/hpx/parallel/algorithms/sort.hpp b/libs/core/algorithms/include/hpx/parallel/algorithms/sort.hpp index 8ee75fcc74f9..8e4bd070994b 100644 --- a/libs/core/algorithms/include/hpx/parallel/algorithms/sort.hpp +++ b/libs/core/algorithms/include/hpx/parallel/algorithms/sort.hpp @@ -187,51 +187,37 @@ namespace hpx::parallel { // \brief this function is the work assigned to each thread in the // parallel process - HPX_CXX_EXPORT template - hpx::future sort_thread(ExPolicy&& policy, RandomIt first, - RandomIt last, Comp comp, std::size_t chunk_size) + HPX_CXX_EXPORT struct sort_thread { - std::ptrdiff_t const N = last - first; - if (static_cast(N) <= chunk_size) + template + hpx::future operator()(ExPolicy&& policy, RandomIt first, + RandomIt last, Comp comp, std::size_t chunk_size) { - return execution::async_execute(policy.executor(), - [first, last, comp = HPX_MOVE(comp)]() -> RandomIt { - std::sort(first, last, comp); - return last; - }); - } + std::ptrdiff_t const N = last - first; + if (static_cast(N) <= chunk_size) + { + return execution::async_execute(policy.executor(), + [first, last, comp = HPX_MOVE(comp)]() -> RandomIt { + std::sort(first, last, comp); + return last; + }); + } - // check if sorted - if (detail::is_sorted_sequential(first, last, comp)) - { - return hpx::make_ready_future(last); - } + // check if sorted + if (detail::is_sorted_sequential(first, last, comp)) + { + return hpx::make_ready_future(last); + } - // pivot selections - pivot9(first, last, comp); + // pivot selections + pivot9(first, last, comp); - using reference = - typename std::iterator_traits::reference; + using reference = + typename std::iterator_traits::reference; - reference val = *first; - RandomIt c_first = first + 1, c_last = last - 1; + reference val = *first; + RandomIt c_first = first + 1, c_last = last - 1; - while (comp(*c_first, val)) - { - ++c_first; - } - while (comp(val, *c_last)) - { - --c_last; - } - while (c_first < c_last) - { -#if defined(HPX_HAVE_CXX20_STD_RANGES_ITER_SWAP) - std::ranges::iter_swap(c_first++, c_last--); -#else - std::iter_swap(c_first++, c_last--); -#endif while (comp(*c_first, val)) { ++c_first; @@ -240,40 +226,56 @@ namespace hpx::parallel { { --c_last; } - } - + while (c_first < c_last) + { #if defined(HPX_HAVE_CXX20_STD_RANGES_ITER_SWAP) - std::ranges::iter_swap(first, c_last); + std::ranges::iter_swap(c_first++, c_last--); #else - std::iter_swap(first, c_last); + std::iter_swap(c_first++, c_last--); #endif - - // spawn tasks for each sub section - hpx::future left = execution::async_execute( - policy.executor(), &sort_thread, - policy, first, c_last, comp, chunk_size); - - hpx::future right = execution::async_execute( - policy.executor(), &sort_thread, - policy, c_first, last, comp, chunk_size); - - return hpx::dataflow( - [last](hpx::future&& leftf, - hpx::future&& rightf) -> RandomIt { - if (leftf.has_exception() || rightf.has_exception()) + while (comp(*c_first, val)) { - std::list errors; - if (leftf.has_exception()) - errors.push_back(leftf.get_exception_ptr()); - if (rightf.has_exception()) - errors.push_back(rightf.get_exception_ptr()); - - throw exception_list(HPX_MOVE(errors)); + ++c_first; } - return last; - }, - HPX_MOVE(left), HPX_MOVE(right)); - } + while (comp(val, *c_last)) + { + --c_last; + } + } + +#if defined(HPX_HAVE_CXX20_STD_RANGES_ITER_SWAP) + std::ranges::iter_swap(first, c_last); +#else + std::iter_swap(first, c_last); +#endif + + // spawn tasks for each subsection + hpx::future left = + execution::async_execute(policy.executor(), *this, policy, + first, c_last, comp, chunk_size); + + hpx::future right = + execution::async_execute(policy.executor(), *this, policy, + c_first, last, comp, chunk_size); + + return hpx::dataflow( + [last](hpx::future&& leftf, + hpx::future&& rightf) -> RandomIt { + if (leftf.has_exception() || rightf.has_exception()) + { + std::list errors; + if (leftf.has_exception()) + errors.push_back(leftf.get_exception_ptr()); + if (rightf.has_exception()) + errors.push_back(rightf.get_exception_ptr()); + + throw exception_list(HPX_MOVE(errors)); + } + return last; + }, + HPX_MOVE(left), HPX_MOVE(right)); + } + }; // policy : execution policy // [in] first iterator to the first element to sort @@ -324,8 +326,7 @@ namespace hpx::parallel { return hpx::make_ready_future(last); } - return execution::async_execute(policy.executor(), - &sort_thread, RandomIt, Comp>, + return execution::async_execute(policy.executor(), sort_thread{}, HPX_FORWARD(ExPolicy, policy), first, last, HPX_FORWARD(Comp, comp), chunk_size); } diff --git a/libs/core/execution/include/hpx/execution/executors/execution.hpp b/libs/core/execution/include/hpx/execution/executors/execution.hpp index 4dd5554cfe4a..018a1fce89c6 100644 --- a/libs/core/execution/include/hpx/execution/executors/execution.hpp +++ b/libs/core/execution/include/hpx/execution/executors/execution.hpp @@ -185,11 +185,9 @@ namespace hpx::parallel::execution { typename... Ts> struct result { - // clang-format off using type = decltype(call(std::declval(), std::declval(), std::declval(), std::declval()...)); - // clang-format on }; }; @@ -345,8 +343,9 @@ namespace hpx::parallel::execution { if constexpr (is_void) { - async_execute_dispatch(0, HPX_FORWARD(TwoWayExecutor, exec), - HPX_FORWARD(F, f), HPX_FORWARD(Ts, ts)...) + return async_execute_dispatch(0, + HPX_FORWARD(TwoWayExecutor, exec), HPX_FORWARD(F, f), + HPX_FORWARD(Ts, ts)...) .get(); } else @@ -366,9 +365,9 @@ namespace hpx::parallel::execution { }) .get(); } - catch (std::bad_alloc const& ba) + catch (std::bad_alloc const&) { - throw ba; + throw; } catch (...) { @@ -505,11 +504,9 @@ namespace hpx::parallel::execution { typename... Ts> struct result { - // clang-format off using type = decltype(call(std::declval(), std::declval(), std::declval(), std::declval()...)); - // clang-format on }; }; @@ -711,15 +708,14 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT template struct bulk_function_result { - using value_type = - typename hpx::traits::range_traits::value_type; + using value_type = hpx::traits::range_traits::value_type; using type = hpx::util::detail::invoke_deferred_result_t; }; HPX_CXX_EXPORT template using bulk_function_result_t = - typename bulk_function_result::type; + bulk_function_result::type; template struct bulk_async_execute_fn_helper using bulk_execute_result_impl_t = - typename bulk_execute_result_impl::type; + bulk_execute_result_impl::type; HPX_CXX_EXPORT template struct bulk_execute_result @@ -910,7 +906,7 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT template using bulk_execute_result_t = - typename bulk_execute_result::type; + bulk_execute_result::type; /////////////////////////////////////////////////////////////////////// template @@ -936,6 +932,7 @@ namespace hpx::parallel::execution { { execution::sync_execute(exec, f, elem, ts...); } + return; } else { @@ -951,9 +948,9 @@ namespace hpx::parallel::execution { return results; } } - catch (std::bad_alloc const& ba) + catch (std::bad_alloc const&) { - throw ba; + throw; } catch (...) { @@ -1009,11 +1006,9 @@ namespace hpx::parallel::execution { typename... Ts> struct result { - // clang-format off using type = decltype(call(std::declval(), std::declval(), std::declval(), std::declval()...)); - // clang-format on }; }; @@ -1059,9 +1054,9 @@ namespace hpx::parallel::execution { return hpx::unwrap(HPX_MOVE(results)); } } - catch (std::bad_alloc const& ba) + catch (std::bad_alloc const&) { - throw ba; + throw; } catch (...) { @@ -1135,11 +1130,9 @@ namespace hpx::parallel::execution { typename... Ts> struct result { - // clang-format off using type = decltype(call(std::declval(), std::declval(), std::declval(), std::declval()...)); - // clang-format on }; }; @@ -1164,11 +1157,9 @@ namespace hpx::parallel::execution { typename... Ts> struct result { - // clang-format off using type = decltype(call(std::declval(), std::declval(), std::declval(), std::declval()...)); - // clang-format on }; }; } // namespace detail @@ -1297,21 +1288,15 @@ namespace hpx::parallel::execution { { template - static auto call_impl( - hpx::traits::detail::wrap_int, BulkExecutor&& exec, F&& f, - Shape const& shape, Future&& predecessor, - Ts&&... -#if !defined(HPX_COMPUTE_DEVICE_CODE) - ts -#endif - ) -> hpx::traits::executor_future_t> + static auto call_impl(hpx::traits::detail::wrap_int, + [[maybe_unused]] BulkExecutor&& exec, [[maybe_unused]] F&& f, + [[maybe_unused]] Shape const& shape, + [[maybe_unused]] Future&& predecessor, + [[maybe_unused]] Ts&&... ts) + -> hpx::traits::executor_future_t> { #if defined(HPX_COMPUTE_DEVICE_CODE) - HPX_UNUSED(exec); - HPX_UNUSED(f); - HPX_UNUSED(shape); - HPX_UNUSED(predecessor); HPX_ASSERT(false); return hpx::traits::executor_future_t>{}; @@ -1438,10 +1423,8 @@ namespace hpx::parallel::execution { template struct result { - // clang-format off using type = decltype(call( std::declval(), std::declval()...)); - // clang-format on }; }; } // namespace detail @@ -1472,10 +1455,8 @@ namespace hpx::parallel::execution { template struct result { - // clang-format off using type = decltype(call( std::declval(), std::declval()...)); - // clang-format on }; }; } // namespace detail diff --git a/libs/core/execution_base/include/hpx/execution_base/execution.hpp b/libs/core/execution_base/include/hpx/execution_base/execution.hpp index 16d34a4cdfb8..192eb9c0bd84 100644 --- a/libs/core/execution_base/include/hpx/execution_base/execution.hpp +++ b/libs/core/execution_base/include/hpx/execution_base/execution.hpp @@ -109,10 +109,24 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT inline constexpr struct sync_execute_t final : hpx::functional::detail::tag_fallback { - private: + using base_type = hpx::functional::detail::tag_fallback; + template requires(std::invocable) - friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( + HPX_FORCEINLINE constexpr decltype(auto) operator()( + Executor&& exec, F&& f, Ts&&... ts) const + noexcept(noexcept(base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Ts, ts)...))) + { + return base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Ts, ts)...); + } + + private: + // default, unconstrained implementation chosen if no other overloads + // are found + template + friend HPX_FORCEINLINE constexpr decltype(auto) tag_fallback_invoke( sync_execute_t, Executor&& exec, F&& f, Ts&&... ts) { return detail::sync_execute_fn_helper>::call( @@ -152,10 +166,25 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT inline constexpr struct async_execute_t final : hpx::functional::detail::tag_fallback { - private: + using base_type = + hpx::functional::detail::tag_fallback; + template requires(std::invocable) - friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( + HPX_FORCEINLINE constexpr decltype(auto) operator()( + Executor&& exec, F&& f, Ts&&... ts) const + noexcept(noexcept(base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Ts, ts)...))) + { + return base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Ts, ts)...); + } + + private: + // default, unconstrained implementation chosen if no other overloads + // are found + template + friend HPX_FORCEINLINE constexpr decltype(auto) tag_fallback_invoke( async_execute_t, Executor&& exec, F&& f, Ts&&... ts) { return detail::async_execute_fn_helper< @@ -187,10 +216,27 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT inline constexpr struct then_execute_t final : hpx::functional::detail::tag_fallback { - private: + using base_type = hpx::functional::detail::tag_fallback; + template requires(std::invocable) + HPX_FORCEINLINE constexpr decltype(auto) operator()( + Executor&& exec, F&& f, Future&& predecessor, Ts&&... ts) const + noexcept(noexcept(base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Future, predecessor), + HPX_FORWARD(Ts, ts)...))) + { + return base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Future, predecessor), + HPX_FORWARD(Ts, ts)...); + } + + private: + // default, unconstrained implementation chosen if no other overloads + // are found + template friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( then_execute_t, Executor&& exec, F&& f, Future&& predecessor, Ts&&... ts) @@ -225,9 +271,23 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT inline constexpr struct post_t final : hpx::functional::detail::tag_fallback { - private: + using base_type = hpx::functional::detail::tag_fallback; + template requires(std::invocable) + HPX_FORCEINLINE constexpr decltype(auto) operator()( + Executor&& exec, F&& f, Ts&&... ts) const + noexcept(noexcept(base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Ts, ts)...))) + { + return base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Ts, ts)...); + } + + private: + // default, unconstrained implementation chosen if no other overloads are + // found + template friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( post_t, Executor&& exec, F&& f, Ts&&... ts) { @@ -242,6 +302,27 @@ namespace hpx::parallel::execution { // execution::bulk_async_execute, execution::bulk_sync_execute, // execution::bulk_then_execute + // Compute the type the bulk function is invoked with + namespace detail { + + template + struct shape_value_type + { + using type = Shape; + }; + + template + struct shape_value_type>> + { + using iterator = hpx::traits::range_iterator_t; + using type = std::iterator_traits::value_type; + }; + } // namespace detail + + HPX_CXX_EXPORT template + using shape_value_type_t = detail::shape_value_type::type; + /// Bulk form of synchronous execution agent creation. /// /// \note This is deliberately different from the bulk_sync_execute @@ -279,9 +360,25 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT inline constexpr struct bulk_sync_execute_t final : hpx::functional::detail::tag_fallback { - private: + using base_type = + hpx::functional::detail::tag_fallback; + template + requires(std::invocable, Ts && ...>) + HPX_FORCEINLINE constexpr decltype(auto) operator()( + Executor&& exec, F&& f, Shape const& shape, Ts&&... ts) const + noexcept(noexcept(base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), shape, HPX_FORWARD(Ts, ts)...))) + { + return base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), shape, HPX_FORWARD(Ts, ts)...); + } + + private: + // default, unconstrained implementations chosen if no other overloads + // are found + template requires(!std::integral) friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( bulk_sync_execute_t, Executor&& exec, F&& f, Shape const& shape, @@ -292,8 +389,7 @@ namespace hpx::parallel::execution { HPX_FORWARD(F, f), shape, HPX_FORWARD(Ts, ts)...); } - template + template requires(std::integral) friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( bulk_sync_execute_t tag, Executor&& exec, F&& f, Shape const& shape, @@ -340,7 +436,24 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT inline constexpr struct bulk_async_execute_t final : hpx::functional::detail::tag_fallback { + using base_type = + hpx::functional::detail::tag_fallback; + + template + requires(std::invocable, Ts && ...>) + HPX_FORCEINLINE constexpr decltype(auto) operator()( + Executor&& exec, F&& f, Shape const& shape, Ts&&... ts) const + noexcept(noexcept(base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), shape, HPX_FORWARD(Ts, ts)...))) + { + return base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), shape, HPX_FORWARD(Ts, ts)...); + } + private: + // default, unconstrained implementations chosen if no other overloads + // are found template requires(!std::integral) @@ -405,8 +518,28 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT inline constexpr struct bulk_then_execute_t final : hpx::functional::detail::tag_fallback { - private: + using base_type = + hpx::functional::detail::tag_fallback; + template + requires(std::invocable, Future &&, + Ts && ...>) + HPX_FORCEINLINE constexpr decltype(auto) operator()(Executor&& exec, + F&& f, Shape const& shape, Future&& predecessor, Ts&&... ts) const + noexcept(noexcept(base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), shape, HPX_FORWARD(Future, predecessor), + HPX_FORWARD(Ts, ts)...))) + { + return base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), shape, HPX_FORWARD(Future, predecessor), + HPX_FORWARD(Ts, ts)...); + } + + private: + // default, unconstrained implementations chosen if no other overloads + // are found + template requires(!std::integral) friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( @@ -419,7 +552,7 @@ namespace hpx::parallel::execution { HPX_FORWARD(Ts, ts)...); } - template requires(std::integral) friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( @@ -457,9 +590,23 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT inline constexpr struct async_invoke_t final : hpx::functional::detail::tag_fallback { - private: + using base_type = hpx::functional::detail::tag_fallback; + template requires(std::invocable && (std::invocable && ...)) + HPX_FORCEINLINE constexpr decltype(auto) operator()( + Executor&& exec, F&& f, Fs&&... fs) const + noexcept(noexcept(base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Fs, fs)...))) + { + return base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Fs, fs)...); + } + + private: + // default, unconstrained implementation chosen if no other overloads + // are found + template friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( async_invoke_t, Executor&& exec, F&& f, Fs&&... fs) { @@ -493,7 +640,22 @@ namespace hpx::parallel::execution { HPX_CXX_EXPORT inline constexpr struct sync_invoke_t final : hpx::functional::detail::tag_fallback { + using base_type = hpx::functional::detail::tag_fallback; + + template + requires(std::invocable && (std::invocable && ...)) + HPX_FORCEINLINE constexpr decltype(auto) operator()( + Executor&& exec, F&& f, Fs&&... fs) const + noexcept(noexcept(base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Fs, fs)...))) + { + return base_type::operator()(HPX_FORWARD(Executor, exec), + HPX_FORWARD(F, f), HPX_FORWARD(Fs, fs)...); + } + private: + // default, unconstrained implementation chosen if no other overloads + // are found template requires(std::invocable && (std::invocable && ...)) friend HPX_FORCEINLINE decltype(auto) tag_fallback_invoke( diff --git a/libs/core/executors/include/hpx/executors/parallel_executor.hpp b/libs/core/executors/include/hpx/executors/parallel_executor.hpp index 69d37610ca06..231bc16e5670 100644 --- a/libs/core/executors/include/hpx/executors/parallel_executor.hpp +++ b/libs/core/executors/include/hpx/executors/parallel_executor.hpp @@ -644,7 +644,8 @@ namespace hpx::execution { // support all properties exposed by the embedded policy HPX_CXX_EXPORT template )> + hpx::execution::experimental::is_scheduling_property_v&& + hpx::functional::is_tag_invocable_v)> auto tag_invoke( Tag tag, parallel_policy_executor const& exec, Property&& prop) -> decltype(std::declval>().policy( @@ -659,7 +660,8 @@ namespace hpx::execution { HPX_CXX_EXPORT template )> + hpx::execution::experimental::is_scheduling_property_v&& + hpx::functional::is_tag_invocable_v)> auto tag_invoke(Tag tag, parallel_policy_executor const& exec) -> decltype(std::declval()(std::declval())) { diff --git a/libs/core/tag_invoke/include/hpx/functional/detail/tag_fallback_invoke.hpp b/libs/core/tag_invoke/include/hpx/functional/detail/tag_fallback_invoke.hpp index 87a5325a6248..23dd2a065506 100644 --- a/libs/core/tag_invoke/include/hpx/functional/detail/tag_fallback_invoke.hpp +++ b/libs/core/tag_invoke/include/hpx/functional/detail/tag_fallback_invoke.hpp @@ -326,7 +326,7 @@ namespace hpx::functional::detail { !is_nothrow_tag_invocable_v && meta::value>>> - HPX_HOST_DEVICE HPX_FORCEINLINE constexpr auto operator()( + HPX_HOST_DEVICE HPX_FORCEINLINE constexpr decltype(auto) operator()( Args&&... args) const noexcept { if constexpr (is_nothrow_tag_fallback_invocable_v(*this), HPX_FORWARD(Args, args)...); } - // Is not nothrow tag-override-invocable, but nothrow - // tag-invocable + // Is not nothrow tag-override-invocable, but nothrow tag-invocable template && @@ -340,8 +339,8 @@ namespace hpx::functional::detail { static_cast(*this), HPX_FORWARD(Args, args)...); } - // Is not nothrow tag-override-invocable, not nothrow - // tag-invocable, but nothrow tag-fallback-invocable + // Is not nothrow tag-override-invocable, not nothrow tag-invocable, + // but nothrow tag-fallback-invocable template &&