within algorithm from P3955#288
Conversation
Also add additional concepts from the paper: - `enter_scope_sender` - `enter_scope_sender_in` - `exit_scope_sender` - `exit_scope_sender_in` - `exit_scope_sender_of_t` Add utilities: - `completion_storage` -- allows storing the completions of an async operation to be used later. - `elide` -- helper to elide move ctor; useful for constructing operation states
Fix issue when checking nothrow by connecting to the wrong receiver.
- Storing a cvref-removed receiver in `within` state object - Initialize the receiver before the operation state -- this caused UB - add appropriate headers in `exec-within.test.cpp` - remove unused variables from test
dietmarkuehl
left a comment
There was a problem hiding this comment.
I'm not sure that I understand what within actually does (I should read the paper). There are a few comments which would be cool to address: in particular I think the single sender tools already exist and some of the other tools seem useful otherwise, too.
| if constexpr (std::is_nothrow_constructible_v<tuple_type, Tag, Args&&...>) { | ||
| storage.template emplace<tuple_type>(t, ::std::forward<Args>(args)...); | ||
| } else { | ||
| try { | ||
| storage.template emplace<tuple_type>(t, ::std::forward<Args>(args)...); | ||
| } catch (...) { | ||
| store_into_variant(storage, ::beman::execution::set_error_t{}, ::std::current_exception()); | ||
| } |
There was a problem hiding this comment.
The "normal" pattern is to always use a try/catch block but execute the error work conditionally, thereby avoiding to duplicate the actual business logic (often the nothrow condition is stored into a constexpr variable):
try {
storage.template emplace<tuple_type>(t, ::std::forward<Args>(args)...);
}
catch (...) {
if constexpr (!std::is_nothrow_constructible_v<tuple_type, Tag, Args&&...>) {
store_into_variant(storage, ::beman::execution::set_error_t{}, ::std::current_exception());
}
}
| ::beman::execution::detail::unreachable(); | ||
| } else { | ||
| ::std::apply( | ||
| [&](const auto tag, auto&&... args) { |
There was a problem hiding this comment.
I guess this function wants to be noexcept as it just passes along references.
| tag(::std::forward<Receiver>(receiver), ::std::forward<decltype(args)>(args)...); | ||
| }, | ||
| // Make a copy, in case the tuple is destroyed by the completion call. | ||
| tuple_type{::std::move(tuple_data)}); |
There was a problem hiding this comment.
... and this move construction may possible throw in which case the exception should probably be caught and turned into a set_error(..., std::current_exception()) result.
| using result_t = std::invoke_result_t<F>; | ||
|
|
||
| /// Constructs the elision wrapper from callable `f`. | ||
| elide(F&& f) noexcept : f_(::std::forward<F>(f)) {} |
There was a problem hiding this comment.
Should the ctor be conditionally noexcept() based on the construction of f_ from F?
| /// A receiver with environment `Env` that accepts every possible completion. | ||
| template <typename Env = ::beman::execution::env<>> | ||
| struct generic_receiver { | ||
| using receiver_concept = ::beman::execution::receiver_tag; | ||
|
|
||
| template <typename... T> | ||
| void set_value(T&&...) && noexcept {} | ||
| template <typename E> | ||
| void set_error(E&&) && noexcept {} | ||
| void set_stopped() && noexcept {} | ||
|
|
||
| auto get_env() const noexcept -> Env { ::beman::execution::detail::unreachable(); } | ||
| }; |
There was a problem hiding this comment.
Is this tool really specific to the exit_scope_sender_in or should it go into a separate header/component?
| template <typename Sender, typename... Env> | ||
| concept nothrow_connectable = | ||
| ::beman::execution::detail::nothrow_callable<::beman::execution::connect_t, Sender, generic_receiver<Env...>>; | ||
|
|
||
| } // namespace beman::execution::detail |
There was a problem hiding this comment.
Likewise nothrow_connectable seems to be separately useful tool which should go into its own header/module.
| template <typename Sig> | ||
| inline constexpr bool is_value_completion_sig = false; | ||
| template <typename... Args> | ||
| inline constexpr bool is_value_completion_sig<::beman::execution::set_value_t(Args...)> = true; |
There was a problem hiding this comment.
There is something like that in include/beman/execution/detail/completion_signature.hpp: is_set_value. It isn't a bool variable template, though. The specialization is certainly needed just once and maybe its use should be unified.
| template <typename... Sigs> | ||
| struct single_value_sender; | ||
|
|
||
| template <typename T, typename... Rest> | ||
| requires(!(::beman::execution::detail::is_value_completion_sig<Rest> || ...)) | ||
| struct single_value_sender<::beman::execution::set_value_t(T), Rest...> { | ||
| using type = T; | ||
| }; | ||
|
|
||
| template <typename First, typename... Rest> | ||
| requires(!::beman::execution::detail::is_value_completion_sig<First>) | ||
| struct single_value_sender<First, Rest...> : ::beman::execution::detail::single_value_sender<Rest...> {}; | ||
|
|
There was a problem hiding this comment.
There is a concept single_sender which could probably do some of the work. It seems, single_value_sender also extracts the argument type, though.
single_sender is an exposition-only concept from the specification. It actually uses single_sender_value_type which is an exposition-only type alias also from the specification. I'd think these can be used directly.
| } | ||
|
|
||
| private: | ||
| /// Helper that yield the signatures of the `within` sender `Sender` when used with the environment `Env`. |
There was a problem hiding this comment.
| /// Helper that yield the signatures of the `within` sender `Sender` when used with the environment `Env`. | |
| /// Helper that yields the signatures of the `within` sender `Sender` when used with the environment `Env`. |
Also add additional concepts from the paper:
enter_scope_senderenter_scope_sender_inexit_scope_senderexit_scope_sender_inexit_scope_sender_of_tAdd utilities:
completion_storage-- allows storing the completions of an async operation to be used later.elide-- helper to elide move ctor; useful for constructing operation states