diff --git a/libs/core/execution/include/hpx/execution/queries/read.hpp b/libs/core/execution/include/hpx/execution/queries/read.hpp index 99d20e4f7d4b..4ce11e124bde 100644 --- a/libs/core/execution/include/hpx/execution/queries/read.hpp +++ b/libs/core/execution/include/hpx/execution/queries/read.hpp @@ -84,11 +84,23 @@ namespace hpx::execution::experimental { HPX_FORWARD(Receiver, receiver)}; } + template + friend auto tag_invoke( + get_completion_signatures_t, read_sender, no_env) + -> dependent_completion_signatures; + // clang-format off template friend auto tag_invoke(get_completion_signatures_t, read_sender, Env) { - if constexpr (hpx::is_invocable_v) + if constexpr (hpx::is_nothrow_invocable_v) + { + using result_type = + completion_signatures< + set_value_t(hpx::util::invoke_result)>; + return result_type{}; + } + else if constexpr (hpx::is_invocable_v) { using result_type = completion_signatures< @@ -105,6 +117,40 @@ namespace hpx::execution::experimental { }; } // namespace detail + // execution::read is used to create a sender that retrieves a value from + // the receiver's associated environment and sends it back to the receiver + // through the value channel. + // + // execution::read is a customization point object of an unspecified class + // type + // + // Returns a sender that reaches into a receiver's environment and pulls out + // the current value associated with the customization point denoted by Tag. + // It then sends the value read back to the receiver through the value + // channel. For instance, get_scheduler() (with no arguments) is a sender + // that asks the receiver for the currently suggested scheduler and passes + // it to the receiver's set_value completion-signal. + // + // This can be useful when scheduling nested dependent work. The following + // sender pulls the current scheduler into the value channel and then + // schedules more work onto it. E.g. + // + // execution::sender auto task = + // execution::get_scheduler() + // | execution::let_value( + // [](auto sched) { + // return execution::on(sched, some nested work here); + // }); + // + // this_thread::sync_wait(std::move(task)); // wait for it to finish + // + // This code uses the fact that sync_wait associates a scheduler with the + // receiver that it connects with task. get_scheduler() reads that scheduler + // out of the receiver, and passes it to let_value's receiver's set_value + // function, which in turn passes it to the lambda. That lambda returns a + // new sender that uses the scheduler to schedule some nested work onto + // sync_wait's scheduler. + // inline constexpr struct read_t final : hpx::functional::tag { private: diff --git a/libs/core/tag_invoke/include/hpx/functional/traits/is_invocable.hpp b/libs/core/tag_invoke/include/hpx/functional/traits/is_invocable.hpp index 6af36e9e7126..a31f5f69d091 100644 --- a/libs/core/tag_invoke/include/hpx/functional/traits/is_invocable.hpp +++ b/libs/core/tag_invoke/include/hpx/functional/traits/is_invocable.hpp @@ -69,4 +69,29 @@ namespace hpx { template inline constexpr bool is_invocable_r_v = is_invocable_r::value; + + namespace detail { + + template + struct is_nothrow_invocable_impl : std::false_type + { + }; + + template + struct is_nothrow_invocable_impl + : std::integral_constant()(std::declval()...))> + { + }; + } // namespace detail + + template + struct is_nothrow_invocable + : detail::is_nothrow_invocable_impl> + { + }; + + template + inline constexpr bool is_nothrow_invocable_v = + is_nothrow_invocable::value; } // namespace hpx