From aa8d882283cab4bc9cfd6a103987a81b2669506a Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Tue, 30 Sep 2025 15:28:50 +0000 Subject: [PATCH 01/39] Add debug and static-member-fn support to sequence-sender - add `__debug_sequence_sender` - add `__well_formed_sequence_sender` - add static-member-fn support to `get_item_types` - add static-member-fn support to `subscribe` - add support for `STDEXEC_ENABLE_EXTRA_TYPE_CHECKING` --- include/exec/sequence_senders.hpp | 569 +++++++++++++++++++++++------- 1 file changed, 435 insertions(+), 134 deletions(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 3f12e1305..3169529c3 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -17,6 +17,8 @@ #pragma once #include "../stdexec/execution.hpp" +#include "stdexec/__detail/__concepts.hpp" +#include "stdexec/__detail/__meta.hpp" namespace exec { struct sequence_sender_t : stdexec::sender_t { }; @@ -31,20 +33,22 @@ namespace exec { template using __f = __mand<__mapply<__mcontains<_Needles>, _Haystack>...>; }; - template using __mall_contained_in = __mapply<__mall_contained_in_impl<_Haystack>, _Needles>; template concept __all_contained_in = __v<__mall_contained_in<_Needles, _Haystack>>; + } // namespace __sequence_sndr // This concept checks if a given sender satisfies the requirements to be returned from `set_next`. - template > - concept next_sender = sender_in<_Sender, _Env> - && __all_contained_in< - completion_signatures_of_t<_Sender, _Env>, - completion_signatures - >; + template > + concept next_sender = stdexec::sender_in<_Sender, _Env> + && __sequence_sndr::__all_contained_in< + stdexec::completion_signatures_of_t<_Sender, _Env>, + stdexec::completion_signatures + >; + + namespace __sequence_sndr { template concept __has_set_next_member = requires(_Receiver& __rcvr, _Item&& __item) { @@ -83,10 +87,10 @@ namespace exec { using __sequence_sndr::set_next_t; inline constexpr set_next_t set_next; - template + template using next_sender_of_t = decltype(exec::set_next( stdexec::__declval&>(), - stdexec::__declval<_Sender>())); + stdexec::__declval<_Sequence>())); namespace __sequence_sndr { @@ -131,80 +135,118 @@ namespace exec { using __stopped_means_break_t = __t<__stopped_means_break<__id<__decay_t<_Rcvr>>>>; } // namespace __sequence_sndr - template + template concept __enable_sequence_sender = requires { - typename _Sender::sender_concept; - } && stdexec::derived_from; + typename _Sequence::sender_concept; + } && stdexec::derived_from; - template - inline constexpr bool enable_sequence_sender = __enable_sequence_sender<_Sender>; + template + inline constexpr bool enable_sequence_sender = __enable_sequence_sender<_Sequence>; template struct item_types { }; + template + concept __has_item_typedef = requires { typename _Tp::item_types; }; + + namespace __debug { + using namespace stdexec::__debug; + + struct __item_types { }; + } // namespace __debug + ///////////////////////////////////////////////////////////////////////////// - // [execution.sndtraits] + // [execution.seqtraits] namespace __sequence_sndr { struct get_item_types_t; - template - using __tfx_sender = - transform_sender_result_t<__late_domain_of_t<_Sender, _Env>, _Sender, _Env>; - template - concept __with_tag_invoke = tag_invocable, _Env>; + template + using __item_types_of_t = + __call_result_t; - template - using __member_alias_t = typename __decay_t<__tfx_sender<_Sender, _Env>>::item_types; + namespace __errs { + inline constexpr __mstring __unrecognized_sequence_type_diagnostic = + "The given type cannot be used as a sequence with the given environment " + "because the attempt to compute the item types failed."_mstr; + } // namespace __errs - template - concept __with_member_alias = __mvalid<__member_alias_t, _Sender, _Env>; + template + struct _WITH_SEQUENCE_; - template - concept __with_member = requires(__tfx_sender<_Sender, _Env>&& __sndr, _Env&& __env) { - static_cast<__tfx_sender<_Sender, _Env> &&>(__sndr) - .get_item_types(static_cast<_Env &&>(__env)); - }; + template <__mstring _Diagnostic = __errs::__unrecognized_sequence_type_diagnostic> + struct _UNRECOGNIZED_SEQUENCE_TYPE_; + + template + using __unrecognized_sequence_error = + __mexception<_UNRECOGNIZED_SEQUENCE_TYPE_<>, _WITH_SEQUENCE_<_Sequence>, _WITH_ENVIRONMENT_<_Env>...>; + + template + using __member_result_t = decltype(__declval<_Sequence>() + .get_item_types(__declval<_Env>())); + + template + using __static_member_result_t = decltype(STDEXEC_REMOVE_REFERENCE( + _Sequence)::get_item_types(__declval<_Sequence>(), __declval<_Env>())); + + template + using __tfx_sequence = + transform_sender_result_t<__late_domain_of_t<_Sequence, _Env>, _Sequence, _Env>; + + template + concept __with_tag_invoke = tag_invocable, _Env>; + + template + using __member_alias_t = typename __decay_t<__tfx_sequence<_Sequence, _Env>>::item_types; + + template + concept __with_member_alias = __mvalid<__member_alias_t, _Sequence, _Env>; + + template + concept __with_static_member = __mvalid<__static_member_result_t, _Sequence, _Env>; + + template + concept __with_member = __mvalid<__member_result_t, _Sequence, _Env...>; struct get_item_types_t { - template + template static auto __impl() { - static_assert(sizeof(_Sender), "Incomplete type used with get_item_types"); + static_assert(sizeof(_Sequence), "Incomplete type used with get_item_types"); static_assert(sizeof(_Env), "Incomplete type used with get_item_types"); - using _TfxSender = __tfx_sender<_Sender, _Env>; - if constexpr (__merror<_TfxSender>) { + using _TfxSequence = __tfx_sequence<_Sequence, _Env>; + if constexpr (__merror<_TfxSequence>) { // Computing the type of the transformed sender returned an error type. Propagate it. - return static_cast<_TfxSender (*)()>(nullptr); - } else if constexpr (__with_member_alias<_Sender, _Env>) { - using _Result = __member_alias_t<_Sender, _Env>; + return static_cast<_TfxSequence (*)()>(nullptr); + } else if constexpr (__with_member_alias<_TfxSequence, _Env>) { + using _Result = __member_alias_t<_TfxSequence, _Env>; + return static_cast<_Result (*)()>(nullptr); + } else if constexpr (__with_static_member<_TfxSequence, _Env>) { + using _Result = __static_member_result_t<_TfxSequence, _Env>; return static_cast<_Result (*)()>(nullptr); - } else if constexpr (__with_member<_Sender, _Env>) { - using _Result = decltype(__declval<_TfxSender>().get_item_types(__declval<_Env>())); + } else if constexpr (__with_member<_TfxSequence, _Env>) { + using _Result = decltype(__declval<_TfxSequence>().get_item_types(__declval<_Env>())); return static_cast<_Result (*)()>(nullptr); - } else if constexpr (__with_tag_invoke<_Sender, _Env>) { - using _Result = tag_invoke_result_t; + } else if constexpr (__with_tag_invoke<_TfxSequence, _Env>) { + using _Result = tag_invoke_result_t; return static_cast<_Result (*)()>(nullptr); } else if constexpr ( - sender_in<_TfxSender, _Env> && !enable_sequence_sender>) { - using _Result = item_types>; + sender_in<_TfxSequence, _Env> && !enable_sequence_sender>) { + using _Result = item_types>; return static_cast<_Result (*)()>(nullptr); } else if constexpr (__is_debug_env<_Env>) { + using __tag_invoke::tag_invoke; // This ought to cause a hard error that indicates where the problem is. - using _Completions - [[maybe_unused]] = decltype(__declval<_TfxSender>().get_item_types(__declval<_Env>())); - return static_cast<__debug::__completion_signatures (*)()>(nullptr); + using _ItemTypes + [[maybe_unused]] = tag_invoke_result_t; + return static_cast<__debug::__item_types (*)()>(nullptr); } else { - using _Result = __mexception< - _UNRECOGNIZED_SENDER_TYPE_<>, - _WITH_SENDER_<_Sender>, - _WITH_ENVIRONMENT_<_Env> - >; + using _Result = __unrecognized_sequence_error<_Sequence, _Env>; return static_cast<_Result (*)()>(nullptr); } } - template > + template > constexpr auto - operator()(_Sender&&, _Env&& = {}) const noexcept -> decltype(__impl<_Sender, _Env>()()) { + operator()(_Sequence&&, _Env&& = {}) const noexcept -> decltype(__impl<_Sequence, _Env>()()) { return {}; } }; @@ -213,22 +255,87 @@ namespace exec { using __sequence_sndr::get_item_types_t; inline constexpr get_item_types_t get_item_types{}; - template - using item_types_of_t = - decltype(get_item_types(stdexec::__declval<_Sender>(), stdexec::__declval<_Env>()...)); - - template - concept sequence_sender = stdexec::sender_in<_Sender, _Env...> - && enable_sequence_sender>; + template + concept sequence_sender = stdexec::sender_in<_Sequence, _Env...> + && enable_sequence_sender>; - template - concept has_sequence_item_types = requires(_Sender&& __sndr, _Env&&... __env) { - get_item_types(static_cast<_Sender &&>(__sndr), static_cast<_Env &&>(__env)...); + template + concept has_sequence_item_types = requires(_Sequence&& __sequence, _Env&&... __env) { + { get_item_types(static_cast<_Sequence &&>(__sequence), static_cast<_Env &&>(__env)...) }; }; - template - concept sequence_sender_in = sequence_sender<_Sender, _Env...> - && has_sequence_item_types<_Sender, _Env...>; + template + concept sequence_sender_in = sequence_sender<_Sequence, _Env...> + && has_sequence_item_types<_Sequence, _Env...>; + + template + using __item_types_of_t = + decltype(get_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); + + + template + struct _SEQUENCE_ITEM_IS_NOT_A_WELL_FORMED_SENDER_ { }; + + template + auto __check_item(_Item*) -> stdexec::__mexception< + _SEQUENCE_ITEM_IS_NOT_A_WELL_FORMED_SENDER_<_Item>, + __sequence_sndr::_WITH_SEQUENCE_<_Sequence> + >; + + template + requires stdexec::__well_formed_sender<_Item> + auto __check_item(_Item*) -> stdexec::__msuccess; + + template + requires stdexec::__merror<_Items> + auto __check_items(_Items*) -> _Items; + + template + struct _SEQUENCE_GET_ITEM_TYPES_RESULT_IS_NOT_WELL_FORMED_ { }; + + template + requires (!stdexec::__merror<_Items>) + auto __check_items(_Items*) -> stdexec::__mexception< + _SEQUENCE_GET_ITEM_TYPES_RESULT_IS_NOT_WELL_FORMED_<_Items>, + __sequence_sndr::_WITH_SEQUENCE_<_Sequence> + >; + + template + auto __check_items(exec::item_types<_Items...>*) -> decltype(( + stdexec::__msuccess(), + ..., + exec::__check_item<_Sequence>(static_cast<_Items*>(nullptr)))); + + template + requires stdexec::__merror<_Sequence> + auto __check_sequence(_Sequence*) -> _Sequence; + + struct _SEQUENCE_GET_ITEM_TYPES_IS_NOT_WELL_FORMED_ {}; + + template + requires (!stdexec::__merror<_Sequence>) + && (!stdexec::__mvalid<__item_types_of_t, _Sequence>) + auto __check_sequence(_Sequence*) -> stdexec::__mexception< + _SEQUENCE_GET_ITEM_TYPES_IS_NOT_WELL_FORMED_, + __sequence_sndr::_WITH_SEQUENCE_<_Sequence> + >; + + template + requires (!stdexec::__merror<_Sequence>) + && stdexec::__mvalid<__item_types_of_t, _Sequence> + auto __check_sequence(_Sequence*) -> decltype( + exec::__check_items<_Sequence>(static_cast<__item_types_of_t<_Sequence>*>(nullptr))); + + template + concept __well_formed_item_senders = has_sequence_item_types> + && requires(stdexec::__decay_t<_Sequence>* __sequence) { + { exec::__check_sequence(__sequence) } -> stdexec::__ok; + }; + + template + concept __well_formed_sequence_sender = stdexec::__well_formed_sender<_Sequence> + && enable_sequence_sender> + && __well_formed_item_senders<_Sequence>; template struct _WITH_RECEIVER_ { }; @@ -293,29 +400,34 @@ namespace exec { __sequence_completion_signatures<_Sequence, _Env...> > >, - item_types_of_t<_Sequence, _Env...> + __item_types_of_t<_Sequence, _Env...> >; - template + template concept sequence_receiver_from = stdexec::receiver<_Receiver> - && stdexec::sender_in<_Sender, stdexec::env_of_t<_Receiver>> + && stdexec::sender_in<_Sequence, stdexec::env_of_t<_Receiver>> && sequence_receiver_of< _Receiver, - item_types_of_t<_Sender, stdexec::env_of_t<_Receiver>> + __item_types_of_t<_Sequence, stdexec::env_of_t<_Receiver>> > - && ((sequence_sender_in<_Sender, stdexec::env_of_t<_Receiver>> + && ((sequence_sender_in<_Sequence, stdexec::env_of_t<_Receiver>> && stdexec::receiver_of< _Receiver, stdexec::completion_signatures_of_t< - _Sender, + _Sequence, stdexec::env_of_t<_Receiver> > >) - || (!sequence_sender_in<_Sender, stdexec::env_of_t<_Receiver>> && stdexec::__receiver_from<__sequence_sndr::__stopped_means_break_t<_Receiver>, next_sender_of_t<_Receiver, _Sender>>) ); + || (!sequence_sender_in<_Sequence, stdexec::env_of_t<_Receiver>> + && stdexec::__receiver_from<__sequence_sndr::__stopped_means_break_t<_Receiver>, next_sender_of_t<_Receiver, _Sequence>>) ); namespace __sequence_sndr { struct subscribe_t; + struct _NO_USABLE_SUBSCRIBE_CUSTOMIZATION_FOUND_ { + void operator()() const noexcept = delete; + }; + template using __single_sender_completion_sigs = __if_c< unstoppable_token>, @@ -330,36 +442,47 @@ namespace exec { && sequence_receiver_of<_Receiver, item_types>> && sender_to, __stopped_means_break_t<_Receiver>>; - template - concept __subscribeable_with_member = receiver<_Receiver> - && sequence_sender_in<_Sender, env_of_t<_Receiver>> - && sequence_receiver_from<_Receiver, _Sender> - && requires(_Sender&& __sndr, _Receiver&& __rcvr) { + template + concept __subscribable_with_static_member = receiver<_Receiver> + && sequence_sender_in<_Sequence, env_of_t<_Receiver>> + && sequence_receiver_from<_Receiver, _Sequence> + && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { + { + STDEXEC_REMOVE_REFERENCE(_Sequence) + ::subscribe(static_cast<_Sequence &&>(__sequence), static_cast<_Receiver &&>(__rcvr)) + }; + }; + + template + concept __subscribable_with_member = receiver<_Receiver> + && sequence_sender_in<_Sequence, env_of_t<_Receiver>> + && sequence_receiver_from<_Receiver, _Sequence> + && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { { - static_cast<_Sender &&>(__sndr) + static_cast<_Sequence &&>(__sequence) .subscribe(static_cast<_Receiver &&>(__rcvr)) }; }; - template - concept __subscribeable_with_tag_invoke = receiver<_Receiver> - && sequence_sender_in<_Sender, env_of_t<_Receiver>> - && sequence_receiver_from<_Receiver, _Sender> - && tag_invocable; + template + concept __subscribable_with_tag_invoke = receiver<_Receiver> + && sequence_sender_in<_Sequence, env_of_t<_Receiver>> + && sequence_receiver_from<_Receiver, _Sequence> + && tag_invocable; struct subscribe_t { - template - using __tfx_sndr = __tfx_sender<_Sender, env_of_t<_Receiver>>; + template + using __tfx_sequence = __tfx_sequence<_Sequence, env_of_t<_Receiver>>; - template + template static constexpr auto __select_impl() noexcept { - using _Domain = __late_domain_of_t<_Sender, env_of_t<_Receiver&>>; - constexpr bool _NothrowTfxSender = - __nothrow_callable>; - using _TfxSender = __tfx_sndr<_Sender, _Receiver>; - if constexpr (__next_connectable<_TfxSender, _Receiver>) { + using _Domain = __late_domain_of_t<_Sequence, env_of_t<_Receiver&>>; + constexpr bool _NothrowTfxSequence = + __nothrow_callable>; + using _TfxSequence = __tfx_sequence<_Sequence, _Receiver>; + if constexpr (__next_connectable<_TfxSequence, _Receiver>) { using _Result = connect_result_t< - next_sender_of_t<_Receiver, _TfxSender>, + next_sender_of_t<_Receiver, _TfxSequence>, __stopped_means_break_t<_Receiver> >; static_assert( @@ -367,75 +490,99 @@ namespace exec { "stdexec::connect(sender, receiver) must return a type that " "satisfies the operation_state concept"); constexpr bool _Nothrow = __nothrow_connectable< - next_sender_of_t<_Receiver, _TfxSender>, + next_sender_of_t<_Receiver, _TfxSequence>, __stopped_means_break_t<_Receiver> >; return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr); - } else if constexpr (__subscribeable_with_member<_TfxSender, _Receiver>) { - using _Result = decltype(__declval<_TfxSender>().subscribe(__declval<_Receiver>())); + } else if constexpr (__subscribable_with_static_member<_TfxSequence, _Receiver>) { + using _Result = decltype(STDEXEC_REMOVE_REFERENCE(_TfxSequence):: + subscribe(__declval<_TfxSequence>(), __declval<_Receiver>())); + static_assert( + operation_state<_Result>, + "Sequence::subscribe(sender, receiver) must return a type that " + "satisfies the operation_state concept"); + constexpr bool _Nothrow = _NothrowTfxSequence + && noexcept(STDEXEC_REMOVE_REFERENCE(_TfxSequence) + ::subscribe(__declval<_TfxSequence>(), __declval<_Receiver>())); + return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr); + } else if constexpr (__subscribable_with_member<_TfxSequence, _Receiver>) { + using _Result = decltype(__declval<_TfxSequence>().subscribe(__declval<_Receiver>())); static_assert( operation_state<_Result>, - "Sender::subscribe(sender, receiver) must return a type that " + "Sequence::subscribe(sender, receiver) must return a type that " "satisfies the operation_state concept"); - constexpr bool _Nothrow = _NothrowTfxSender - && noexcept(__declval<_TfxSender>() + constexpr bool _Nothrow = _NothrowTfxSequence + && noexcept(__declval<_TfxSequence>() .subscribe(__declval<_Receiver>())); return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr); - } else if constexpr (__subscribeable_with_tag_invoke<_TfxSender, _Receiver>) { - using _Result = tag_invoke_result_t; + } else if constexpr (__subscribable_with_tag_invoke<_TfxSequence, _Receiver>) { + using _Result = tag_invoke_result_t; static_assert( operation_state<_Result>, "exec::subscribe(sender, receiver) must return a type that " "satisfies the operation_state concept"); - constexpr bool _Nothrow = _NothrowTfxSender - && nothrow_tag_invocable; + constexpr bool _Nothrow = _NothrowTfxSequence + && nothrow_tag_invocable; return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr); + } else if constexpr (__is_debug_env>) { + using _Result = __debug::__debug_operation; + return static_cast<_Result (*)() noexcept(_NothrowTfxSequence)>(nullptr); } else { - return static_cast<__debug::__debug_operation (*)() noexcept>(nullptr); + return _NO_USABLE_SUBSCRIBE_CUSTOMIZATION_FOUND_(); } } - template - using __select_impl_t = decltype(__select_impl<_Sender, _Receiver>()); + template + using __select_impl_t = decltype(__select_impl<_Sequence, _Receiver>()); - template - requires __next_connectable<__tfx_sndr<_Sender, _Receiver>, _Receiver> - || __subscribeable_with_member<__tfx_sndr<_Sender, _Receiver>, _Receiver> - || __subscribeable_with_tag_invoke<__tfx_sndr<_Sender, _Receiver>, _Receiver> + template + requires __next_connectable<__tfx_sequence<_Sequence, _Receiver>, _Receiver> + || __subscribable_with_static_member<__tfx_sequence<_Sequence, _Receiver>, _Receiver> + || __subscribable_with_member<__tfx_sequence<_Sequence, _Receiver>, _Receiver> + || __subscribable_with_tag_invoke<__tfx_sequence<_Sequence, _Receiver>, _Receiver> || __is_debug_env> - auto operator()(_Sender&& __sndr, _Receiver&& __rcvr) const - noexcept(__nothrow_callable<__select_impl_t<_Sender, _Receiver>>) - -> __call_result_t<__select_impl_t<_Sender, _Receiver>> { - using _TfxSender = __tfx_sndr<_Sender, _Receiver>; + auto operator()(_Sequence&& __sequence, _Receiver&& __rcvr) const + noexcept(__nothrow_callable<__select_impl_t<_Sequence, _Receiver>>) + -> __call_result_t<__select_impl_t<_Sequence, _Receiver>> { + using _TfxSequence = __tfx_sequence<_Sequence, _Receiver>; auto&& __env = stdexec::get_env(__rcvr); - auto __domain = __get_late_domain(__sndr, __env); - if constexpr (__next_connectable<_TfxSender, _Receiver>) { - next_sender_of_t<_Receiver, _TfxSender> __next = set_next( - __rcvr, stdexec::transform_sender(__domain, static_cast<_Sender&&>(__sndr), __env)); + auto __domain = __get_late_domain(__sequence, __env); + if constexpr (__next_connectable<_TfxSequence, _Receiver>) { + next_sender_of_t<_Receiver, _TfxSequence> __next = set_next( + __rcvr, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); return stdexec::connect( - static_cast&&>(__next), + static_cast&&>(__next), __stopped_means_break_t<_Receiver>{static_cast<_Receiver&&>(__rcvr)}); // NOLINTNEXTLINE(bugprone-branch-clone) - } else if constexpr (__subscribeable_with_member<_TfxSender, _Receiver>) { - return stdexec::transform_sender(__domain, static_cast<_Sender&&>(__sndr), __env) + } else if constexpr (__subscribable_with_static_member<_TfxSequence, _Receiver>) { + auto&& __tfx_sequence = transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); + return __tfx_sequence + .subscribe( + static_cast<_TfxSequence&&>(__tfx_sequence), + static_cast<_Receiver&&>(__rcvr)); + } else if constexpr (__subscribable_with_member<_TfxSequence, _Receiver>) { + return stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env) .subscribe(static_cast<_Receiver&&>(__rcvr)); - } else if constexpr (__subscribeable_with_tag_invoke<_TfxSender, _Receiver>) { + } else if constexpr (__subscribable_with_tag_invoke<_TfxSequence, _Receiver>) { return stdexec::tag_invoke( subscribe_t{}, - stdexec::transform_sender(__domain, static_cast<_Sender&&>(__sndr), __env), + stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env), static_cast<_Receiver&&>(__rcvr)); - } else if constexpr (enable_sequence_sender>) { + } else if constexpr (enable_sequence_sender>) { // This should generate an instantiate backtrace that contains useful // debugging information. - return stdexec::transform_sender(__domain, static_cast<_Sender&&>(__sndr), __env) - .subscribe(static_cast<_Receiver&&>(__rcvr)); + auto&& __tfx_sequence = transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); + return __tfx_sequence + .subscribe( + static_cast<_TfxSequence&&>(__tfx_sequence), + static_cast<_Receiver&&>(__rcvr)); } else { // This should generate an instantiate backtrace that contains useful // debugging information. - next_sender_of_t<_Receiver, _TfxSender> __next = set_next( - __rcvr, stdexec::transform_sender(__domain, static_cast<_Sender&&>(__sndr), __env)); + next_sender_of_t<_Receiver, _TfxSequence> __next = set_next( + __rcvr, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); return stdexec::connect( - static_cast&&>(__next), + static_cast&&>(__next), __stopped_means_break_t<_Receiver>{static_cast<_Receiver&&>(__rcvr)}); } } @@ -445,8 +592,8 @@ namespace exec { } }; - template - using subscribe_result_t = __call_result_t; + template + using subscribe_result_t = __call_result_t; } // namespace __sequence_sndr using __sequence_sndr::__single_sender_completion_sigs; @@ -456,10 +603,10 @@ namespace exec { using __sequence_sndr::subscribe_result_t; - template + template concept sequence_sender_to = - sequence_receiver_from<_Receiver, _Sender> && requires(_Sender&& __sndr, _Receiver&& __rcvr) { - subscribe(static_cast<_Sender &&>(__sndr), static_cast<_Receiver &&>(__rcvr)); + sequence_receiver_from<_Receiver, _Sequence> && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { + subscribe(static_cast<_Sequence &&>(__sequence), static_cast<_Receiver &&>(__rcvr)); }; template @@ -484,4 +631,158 @@ namespace exec { } } } -} // namespace exec \ No newline at end of file + + //////////////////////////////////////////////////////////////////////////////// +# define STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR \ + "\n" \ + "\n" \ + "Trying to compute the sequences's item types resulted in an error. See\n" \ + "the rest of the compiler diagnostic for clues. Look for the string \"_ERROR_\".\n" + +# define STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE \ + "\n" \ + "\n" \ + "The member function `get_item_types` of the sequence returned an\n" \ + "invalid type.\n" \ + "\n" \ + "A sender's `get_item_types` function must return a specialization of\n" \ + "`exec::item_types<...>`, as follows:\n" \ + "\n" \ + " class MySequence\n" \ + " {\n" \ + " public:\n" \ + " using sender_concept = exec::sequence_sender_t;\n" \ + "\n" \ + " template \n" \ + " auto get_item_types(_Env&&...) -> exec::item_types<\n" \ + " // This sequence produces void items...\n" \ + " stdexec::__call_result_t,\n" \ + " {\n" \ + " return {};\n" \ + " }\n" \ + " ...\n" \ + " };\n" + + // Used to report a meaningful error message when the sender_in + // concept check fails. + template + auto __diagnose_sequence_concept_failure() { + if constexpr (!enable_sequence_sender>) { + static_assert(enable_sequence_sender<_Sequence>, STDEXEC_ERROR_ENABLE_SENDER_IS_FALSE); + } else if constexpr (!stdexec::__detail::__consistent_completion_domains<_Sequence>) { + static_assert( + stdexec::__detail::__consistent_completion_domains<_Sequence>, + "The completion schedulers of the sequence do not have " + "consistent domains. This is likely a " + "bug in the sequence implementation."); + } else if constexpr (!std::move_constructible>) { + static_assert( + std::move_constructible>, "The sequence type is not move-constructible."); + } else if constexpr (!std::constructible_from, _Sequence>) { + static_assert( + std::constructible_from, _Sequence>, + "The sequence cannot be decay-copied. Did you forget a std::move?"); + } else { + using _Items = __item_types_of_t<_Sequence, _Env...>; + if constexpr (stdexec::__same_as<_Items, __sequence_sndr::__unrecognized_sequence_error<_Sequence, _Env...>>) { + static_assert(stdexec::__mnever<_Items>, STDEXEC_ERROR_CANNOT_COMPUTE_COMPLETION_SIGNATURES); + } else if constexpr (stdexec::__merror<_Items>) { + static_assert( + !stdexec::__merror<_Items>, STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR); + } else if constexpr (!__well_formed_item_senders<_Sequence>) { + static_assert( + __well_formed_item_senders<_Sequence>, + STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE); + } else { + stdexec::__diagnose_sender_concept_failure<_Sequence, _Env...>(); + } +#if STDEXEC_MSVC() || STDEXEC_NVHPC() + // MSVC and NVHPC need more encouragement to print the type of the + // error. + _Completions __what = 0; +#endif + } + } + + namespace __debug { + + template + struct __valid_next { + template + requires stdexec::__one_of<_Item, _Items...> + STDEXEC_ATTRIBUTE(host, device) + stdexec::__call_result_t set_next(_Item&&) noexcept { + STDEXEC_TERMINATE(); + return stdexec::just(); + } + }; + + template + struct __debug_sequence_sender_receiver { + using __t = __debug_sequence_sender_receiver; + using __id = __debug_sequence_sender_receiver; + using receiver_concept = stdexec::receiver_t; + }; + + template + struct __debug_sequence_sender_receiver<_CvrefSequenceId, _Env, stdexec::completion_signatures<_Sigs...>, item_types<_Items...>> + : __valid_completions<__normalize_sig_t<_Sigs>...> + , __valid_next<_Items...> { + using __t = __debug_sequence_sender_receiver; + using __id = __debug_sequence_sender_receiver; + using receiver_concept = stdexec::receiver_t; + + STDEXEC_ATTRIBUTE(host, device) auto get_env() const noexcept -> __debug_env_t<_Env> { + STDEXEC_TERMINATE(); + } + }; + + template , class _Sequence> + void __debug_sequence_sender(_Sequence&& __sequence, const _Env& = {}) { + if constexpr (!__is_debug_env<_Env>) { + if constexpr (sequence_sender_in<_Sequence, _Env>) { + using _Sigs = stdexec::__completion_signatures_of_t<_Sequence, __debug_env_t<_Env>>; + using _ItemTypes = __sequence_sndr::__item_types_of_t<_Sequence, __debug_env_t<_Env>>; + using _Receiver = __debug_sequence_sender_receiver, _Env, _Sigs, _ItemTypes>; + if constexpr (!std::same_as<_Sigs, __debug::__completion_signatures> || !std::same_as<_ItemTypes, __debug::__item_types>) { + using _Operation = exec::subscribe_result_t<_Sequence, _Receiver>; + //static_assert(receiver_of<_Receiver, _Sigs>); + if constexpr (!std::same_as<_Operation, __debug_operation>) { + if (sizeof(_Sequence) == ~0ul) { // never true + auto __op = subscribe(static_cast<_Sequence&&>(__sequence), _Receiver{}); + stdexec::start(__op); + } + } + } + } else { + __diagnose_sequence_concept_failure<_Sequence, _Env>(); + } + } + } + } // namespace __debug + using __debug::__debug_sequence_sender; + + #if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() + // __checked_completion_signatures is for catching logic bugs in a sender's metadata. If sender + // and sender_in are both true, then they had better report the same metadata. This + // completion signatures wrapper enforces that at compile time. + template + auto __checked_item_types(_Sequence && __sequence, _Env &&... __env) noexcept { + using __completions_t = + decltype(get_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); + // (void)__sequence; + // [](auto&&...){}(__env...); + exec::__debug_sequence_sender(static_cast<_Sequence &&>(__sequence), __env...); + return __completions_t{}; + } + + template + requires sequence_sender_in<_Sequence, _Env...> + using item_types_of_t = + decltype(exec::__checked_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); +#else + template + requires sequence_sender_in<_Sequence, _Env...> + using item_types_of_t = __item_types_of_t<_Sequence, _Env...>; +#endif +} // namespace exec From 4e37d4d52af6d63d6bcaa1dc3d9c3db4018658dd Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Tue, 30 Sep 2025 15:30:47 +0000 Subject: [PATCH 02/39] switch `__seqexpr` to use static-member-fn to customize `subscribe` and `get_item_types` --- include/exec/__detail/__basic_sequence.hpp | 27 +++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/include/exec/__detail/__basic_sequence.hpp b/include/exec/__detail/__basic_sequence.hpp index ff91f1096..0b2bdf857 100644 --- a/include/exec/__detail/__basic_sequence.hpp +++ b/include/exec/__detail/__basic_sequence.hpp @@ -71,35 +71,36 @@ namespace exec { return {}; } - template _Self, class _Env> - STDEXEC_MEMFN_DECL(auto get_item_types)(this _Self&& __self, _Env&& __env) - -> decltype(__self.__tag() - .get_item_types(static_cast<_Self&&>(__self), static_cast<_Env&&>(__env))) { + template _Self, class... _Env> + static auto get_item_types(_Self&& __self, _Env&&... __env) + -> decltype(__self.__tag().get_item_types( + static_cast<_Self&&>(__self), + static_cast<_Env&&>(__env)...)) { return {}; } template _Self, stdexec::receiver _Receiver> - STDEXEC_MEMFN_DECL(auto subscribe)(this _Self&& __self, _Receiver&& __rcvr) noexcept(noexcept( + static auto subscribe(_Self&& __self, _Receiver&& __rcvr) noexcept(noexcept( __self.__tag().subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)))) -> decltype(__self.__tag() .subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr))) { return __tag_t::subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)); } - template + template static auto - apply(_Sender&& __sndr, _ApplyFn&& __fun) noexcept(stdexec::__nothrow_callable< - stdexec::__detail::__impl_of<_Sender>, - stdexec::__copy_cvref_fn<_Sender>, + apply(_Sequence&& __sequence, _ApplyFn&& __fun) noexcept(stdexec::__nothrow_callable< + stdexec::__detail::__impl_of<_Sequence>, + stdexec::__copy_cvref_fn<_Sequence>, _ApplyFn >) -> stdexec::__call_result_t< - stdexec::__detail::__impl_of<_Sender>, - stdexec::__copy_cvref_fn<_Sender>, + stdexec::__detail::__impl_of<_Sequence>, + stdexec::__copy_cvref_fn<_Sequence>, _ApplyFn > { - return static_cast<_Sender&&>(__sndr) - .__impl_(stdexec::__copy_cvref_fn<_Sender>(), static_cast<_ApplyFn&&>(__fun)); + return static_cast<_Sequence&&>(__sequence) + .__impl_(stdexec::__copy_cvref_fn<_Sequence>(), static_cast<_ApplyFn&&>(__fun)); } }; From fef35d8c7224bfbc89d2e01549febd2d6266bfa7 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Tue, 30 Sep 2025 15:33:43 +0000 Subject: [PATCH 03/39] add `__well_formed_sequence_sender` constraints to `ignore_all_values` and `iterate` --- include/exec/sequence/ignore_all_values.hpp | 11 ++--------- include/exec/sequence/iterate.hpp | 4 ++-- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/include/exec/sequence/ignore_all_values.hpp b/include/exec/sequence/ignore_all_values.hpp index 3444260ff..7c403b692 100644 --- a/include/exec/sequence/ignore_all_values.hpp +++ b/include/exec/sequence/ignore_all_values.hpp @@ -276,8 +276,6 @@ namespace exec { using __completion_sigs = __sequence_completion_signatures_of_t<_Child, _Env>; template - requires receiver_of<_Receiver, __completion_sigs<_Child>> - && sequence_sender_to<_Child, __receiver_t<_Child>> auto operator()(__ignore, __ignore, _Child&& __child) noexcept(__nothrow_constructible_from<__operation_t<_Child>, _Child, _Receiver>) -> __operation_t<_Child> { @@ -287,7 +285,7 @@ namespace exec { struct ignore_all_values_t { template - auto operator()(_Sender&& __sndr) const { + auto operator()(_Sender&& __sndr) const -> __well_formed_sender auto { auto __domain = __get_early_domain(static_cast<_Sender&&>(__sndr)); return transform_sender( __domain, __make_sexpr(__(), static_cast<_Sender&&>(__sndr))); @@ -320,11 +318,6 @@ namespace exec { [](_Sender&& __sndr, _Receiver __rcvr) noexcept( __nothrow_callable<__sexpr_apply_t, _Sender, __connect_fn<_Receiver>>) -> __call_result_t<__sexpr_apply_t, _Sender, __connect_fn<_Receiver>> - requires receiver_of<_Receiver, __completion_sigs<__child_of<_Sender>, env_of_t<_Receiver>>> - && sequence_sender_to< - __child_of<_Sender>, - __receiver_t<__child_of<_Sender>, _Receiver> - > { static_assert(sender_expr_for<_Sender, ignore_all_values_t>); return __sexpr_apply(static_cast<_Sender&&>(__sndr), __connect_fn<_Receiver>{__rcvr}); @@ -340,4 +333,4 @@ namespace stdexec { template <> struct __sexpr_impl : exec::__ignore_all_values::__ignore_all_values_impl { }; -} // namespace stdexec \ No newline at end of file +} // namespace stdexec diff --git a/include/exec/sequence/iterate.hpp b/include/exec/sequence/iterate.hpp index 58a39315c..1a71dd255 100644 --- a/include/exec/sequence/iterate.hpp +++ b/include/exec/sequence/iterate.hpp @@ -167,7 +167,7 @@ namespace exec { struct iterate_t { template requires __decay_copyable<_Range> - auto operator()(_Range&& __range) const { + auto operator()(_Range&& __range) const -> __well_formed_sequence_sender auto { return make_sequence_expr(__decay_t<_Range>{static_cast<_Range&&>(__range)}); } @@ -219,4 +219,4 @@ namespace exec { inline constexpr iterate_t iterate; } // namespace exec -#endif // STDEXEC_HAS_STD_RANGES() \ No newline at end of file +#endif // STDEXEC_HAS_STD_RANGES() From f7da526ca0fe26bffb9a67ccb8ff804243f90079 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Tue, 30 Sep 2025 15:38:44 +0000 Subject: [PATCH 04/39] improve debugging for `transform_each` - add `__well_formed_sequence_sender` constraint - add `__mexception` overloads for `get_item_types` - remove constraints that were causing SFINAE that would skip the debug info --- include/exec/sequence/transform_each.hpp | 113 ++++++++++++++++------- 1 file changed, 82 insertions(+), 31 deletions(-) diff --git a/include/exec/sequence/transform_each.hpp b/include/exec/sequence/transform_each.hpp index 3e3fe05a4..aa183595d 100644 --- a/include/exec/sequence/transform_each.hpp +++ b/include/exec/sequence/transform_each.hpp @@ -21,6 +21,8 @@ #include "../sequence_senders.hpp" #include "../__detail/__basic_sequence.hpp" +#include "stdexec/__detail/__diagnostics.hpp" +#include "stdexec/__detail/__meta.hpp" namespace exec { namespace __transform_each { @@ -42,8 +44,6 @@ namespace exec { __operation_base<_Receiver, _Adaptor>* __op_; template - requires __callable<_Adaptor&, _Item> - && __callable> auto set_next(_Item&& __item) & noexcept( __nothrow_callable> && __nothrow_callable<_Adaptor&, _Item>) @@ -57,7 +57,6 @@ namespace exec { } template - requires __callable void set_error(_Error&& __error) noexcept { stdexec::set_error( static_cast<_Receiver&&>(__op_->__receiver_), static_cast<_Error&&>(__error)); @@ -121,29 +120,37 @@ namespace exec { template struct _WITH_ITEM_SENDER_ { }; - template - auto __try_call(_Item*) -> stdexec::__mexception< - _NOT_CALLABLE_ADAPTOR_<_Adaptor&>, - _WITH_ITEM_SENDER_> - >; + template + struct __try_adaptor_calls_t { + + template + auto operator()(_Item*) -> stdexec::__mexception< + _NOT_CALLABLE_ADAPTOR_<_Adaptor&>, + _WITH_ITEM_SENDER_> + >; - template - requires stdexec::__callable<_Adaptor&, _Item> - auto __try_call(_Item*) -> stdexec::__msuccess; + template + requires stdexec::__callable<_Adaptor&, _Item> + auto operator()(_Item*) -> stdexec::__msuccess; + + template + auto operator()(item_types<_Items...>*) -> decltype(( + stdexec::__msuccess(), ..., (*this)(static_cast<_Items*>(nullptr)))); + }; - template - auto __try_calls(item_types<_Items...>*) -> decltype(( - stdexec::__msuccess() && ... && __try_call<_Adaptor>(static_cast<_Items*>(nullptr)))); + template + using __try_adaptor_calls_result_t = __call_result_t<__try_adaptor_calls_t>, _Items>; template - concept __callabale_adaptor_for = requires(_Items* __items) { - { __try_calls>(__items) } -> stdexec::__ok; + concept __callable_adaptor_for = requires(_Items* __items) { + { __try_adaptor_calls_t>{}(__items) } -> stdexec::__ok; }; struct transform_each_t { template auto operator()(_Sequence&& __sndr, _Adaptor&& __adaptor) const - noexcept(__nothrow_decay_copyable<_Sequence> && __nothrow_decay_copyable<_Adaptor>) { + noexcept(__nothrow_decay_copyable<_Sequence> && __nothrow_decay_copyable<_Adaptor>) + -> __well_formed_sequence_sender auto { return make_sequence_expr( static_cast<_Adaptor&&>(__adaptor), static_cast<_Sequence&&>(__sndr)); } @@ -155,12 +162,12 @@ namespace exec { return {{static_cast<_Adaptor&&>(__adaptor)}, {}, {}}; } - template - using __completion_sigs_t = __sequence_completion_signatures_of_t<__child_of<_Self>, _Env>; + template + using __completion_sigs_t = __sequence_completion_signatures_of_t<__child_of<_Self>, _Env...>; - template _Self, class _Env> + template _Self, class... _Env> static auto - get_completion_signatures(_Self&&, _Env&&) noexcept -> __completion_sigs_t<_Self, _Env> { + get_completion_signatures(_Self&&, _Env&&...) noexcept -> __completion_sigs_t<_Self, _Env...> { return {}; } @@ -170,11 +177,61 @@ namespace exec { stdexec::__mbind_front_q<__call_result_t, __data_of<_Self>&>, stdexec::__munique> >, - item_types_of_t<__child_of<_Self>, _Env...> + __item_types_of_t<__child_of<_Self>, _Env...> >; - template _Self, class _Env> - static auto get_item_types(_Self&&, _Env&&) noexcept -> __item_types_t<_Self, _Env> { + template + struct _TRANSFORM_EACH_ADAPTOR_INVOCATION_FAILED_ {}; + + template _Self, class... _Env> + requires (!__mvalid<__item_types_t, _Self, _Env...>) + && __mvalid<__item_types_of_t, __child_of<_Self>, _Env...> + && (!__callable_adaptor_for< + __data_of<_Self>, + __item_types_of_t<__child_of<_Self>, _Env...> + >) + static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< + _TRANSFORM_EACH_ADAPTOR_INVOCATION_FAILED_<_Self>, + __sequence_sndr::_WITH_SEQUENCE_<__child_of<_Self>>, + _WITH_ENVIRONMENT_<_Env...>, + _WITH_TYPE_<__try_adaptor_calls_result_t< + __data_of<_Self>, + __item_types_of_t<__child_of<_Self>, _Env...>>>>; + + template + struct _TRANSFORM_EACH_ITEM_TYPES_OF_THE_CHILD_ARE_INVALID_ {}; + + template _Self, class... _Env> + requires (!__mvalid<__item_types_t, _Self, _Env...>) + && (!__mvalid<__item_types_of_t, __child_of<_Self>, _Env...>) + static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< + _TRANSFORM_EACH_ITEM_TYPES_OF_THE_CHILD_ARE_INVALID_<_Self>, + __sequence_sndr::_WITH_SEQUENCE_<__child_of<_Self>>, + _WITH_ENVIRONMENT_<_Env...>>; + + template + struct _TRANSFORM_EACH_ITEM_TYPES_CALCULATION_FAILED_ {}; + + template _Self, class... _Env> + requires (!__mvalid<__item_types_t, _Self, _Env...>) + && __mvalid<__item_types_of_t, __child_of<_Self>, _Env...> + && __callable_adaptor_for< + __data_of<_Self>, + __item_types_of_t<__child_of<_Self>, _Env...> + > + static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< + _TRANSFORM_EACH_ITEM_TYPES_CALCULATION_FAILED_<_Self>, + __sequence_sndr::_WITH_SEQUENCE_<__child_of<_Self>>, + _WITH_ENVIRONMENT_<_Env...>>; + + template _Self, class... _Env> + requires __mvalid<__item_types_t, _Self, _Env...> + && __mvalid<__item_types_of_t, __child_of<_Self>, _Env...> + && __callable_adaptor_for< + __data_of<_Self>, + __item_types_of_t<__child_of<_Self>, _Env...> + > + static auto get_item_types(_Self&&, _Env&&...) noexcept -> __item_types_t<_Self, _Env...> { return {}; } @@ -185,12 +242,6 @@ namespace exec { using __operation_t = __t<__operation<__child_of<_Self>, __id<_Receiver>, __data_of<_Self>>>; template _Self, receiver _Receiver> - requires __callabale_adaptor_for< - __data_of<_Self>, - item_types_of_t<__child_of<_Self>, env_of_t<_Receiver>> - > - && sequence_receiver_of<_Receiver, __item_types_t<_Self, env_of_t<_Receiver>>> - && sequence_sender_to<__child_of<_Self>, __receiver_t<_Self, _Receiver>> static auto subscribe(_Self&& __self, _Receiver __rcvr) noexcept(__nothrow_callable<__sexpr_apply_t, _Self, __subscribe_fn<_Receiver>>) -> __call_result_t<__sexpr_apply_t, _Self, __subscribe_fn<_Receiver>> { @@ -208,4 +259,4 @@ namespace exec { using __transform_each::transform_each_t; inline constexpr transform_each_t transform_each{}; -} // namespace exec \ No newline at end of file +} // namespace exec From 082a9e4e3f367a8bf2f2d5f405c49d572035ea79 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Tue, 30 Sep 2025 20:23:19 +0000 Subject: [PATCH 05/39] move sequence ERROR structs into exec namespace --- include/exec/sequence/transform_each.hpp | 6 ++--- include/exec/sequence_senders.hpp | 34 +++++++++++++----------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/include/exec/sequence/transform_each.hpp b/include/exec/sequence/transform_each.hpp index aa183595d..a09624c5d 100644 --- a/include/exec/sequence/transform_each.hpp +++ b/include/exec/sequence/transform_each.hpp @@ -192,7 +192,7 @@ namespace exec { >) static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< _TRANSFORM_EACH_ADAPTOR_INVOCATION_FAILED_<_Self>, - __sequence_sndr::_WITH_SEQUENCE_<__child_of<_Self>>, + _WITH_SEQUENCE_<__child_of<_Self>>, _WITH_ENVIRONMENT_<_Env...>, _WITH_TYPE_<__try_adaptor_calls_result_t< __data_of<_Self>, @@ -206,7 +206,7 @@ namespace exec { && (!__mvalid<__item_types_of_t, __child_of<_Self>, _Env...>) static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< _TRANSFORM_EACH_ITEM_TYPES_OF_THE_CHILD_ARE_INVALID_<_Self>, - __sequence_sndr::_WITH_SEQUENCE_<__child_of<_Self>>, + _WITH_SEQUENCE_<__child_of<_Self>>, _WITH_ENVIRONMENT_<_Env...>>; template @@ -221,7 +221,7 @@ namespace exec { > static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< _TRANSFORM_EACH_ITEM_TYPES_CALCULATION_FAILED_<_Self>, - __sequence_sndr::_WITH_SEQUENCE_<__child_of<_Self>>, + _WITH_SEQUENCE_<__child_of<_Self>>, _WITH_ENVIRONMENT_<_Env...>>; template _Self, class... _Env> diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 3169529c3..51d2b7e9b 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -155,6 +155,22 @@ namespace exec { struct __item_types { }; } // namespace __debug + namespace __errs { + using namespace stdexec; + inline constexpr __mstring __unrecognized_sequence_type_diagnostic = + "The given type cannot be used as a sequence with the given environment " + "because the attempt to compute the item types failed."_mstr; + } // namespace __errs + + template + struct _WITH_SEQUENCE_; + + template + struct _WITH_SEQUENCES_; + + template + struct _UNRECOGNIZED_SEQUENCE_TYPE_; + ///////////////////////////////////////////////////////////////////////////// // [execution.seqtraits] namespace __sequence_sndr { @@ -164,18 +180,6 @@ namespace exec { using __item_types_of_t = __call_result_t; - namespace __errs { - inline constexpr __mstring __unrecognized_sequence_type_diagnostic = - "The given type cannot be used as a sequence with the given environment " - "because the attempt to compute the item types failed."_mstr; - } // namespace __errs - - template - struct _WITH_SEQUENCE_; - - template <__mstring _Diagnostic = __errs::__unrecognized_sequence_type_diagnostic> - struct _UNRECOGNIZED_SEQUENCE_TYPE_; - template using __unrecognized_sequence_error = __mexception<_UNRECOGNIZED_SEQUENCE_TYPE_<>, _WITH_SEQUENCE_<_Sequence>, _WITH_ENVIRONMENT_<_Env>...>; @@ -279,7 +283,7 @@ namespace exec { template auto __check_item(_Item*) -> stdexec::__mexception< _SEQUENCE_ITEM_IS_NOT_A_WELL_FORMED_SENDER_<_Item>, - __sequence_sndr::_WITH_SEQUENCE_<_Sequence> + _WITH_SEQUENCE_<_Sequence> >; template @@ -297,7 +301,7 @@ namespace exec { requires (!stdexec::__merror<_Items>) auto __check_items(_Items*) -> stdexec::__mexception< _SEQUENCE_GET_ITEM_TYPES_RESULT_IS_NOT_WELL_FORMED_<_Items>, - __sequence_sndr::_WITH_SEQUENCE_<_Sequence> + _WITH_SEQUENCE_<_Sequence> >; template @@ -317,7 +321,7 @@ namespace exec { && (!stdexec::__mvalid<__item_types_of_t, _Sequence>) auto __check_sequence(_Sequence*) -> stdexec::__mexception< _SEQUENCE_GET_ITEM_TYPES_IS_NOT_WELL_FORMED_, - __sequence_sndr::_WITH_SEQUENCE_<_Sequence> + _WITH_SEQUENCE_<_Sequence> >; template From 4cca273e92e7425b2aaad4e3ab21202642d30821 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Tue, 30 Sep 2025 20:26:01 +0000 Subject: [PATCH 06/39] add `merge` algorithm for sequence-senders each input sequence may be on a different scheduler. The merged items will invoke `set_next` on the receiver from all of the contexts. Depending on the schedulers in play, the calls to `set_next` may overlap in parallel. --- include/exec/sequence/merge.hpp | 219 ++++++++++++++++++++++++++ test/exec/CMakeLists.txt | 1 + test/exec/sequence/test_merge.cpp | 247 ++++++++++++++++++++++++++++++ 3 files changed, 467 insertions(+) create mode 100644 include/exec/sequence/merge.hpp create mode 100644 test/exec/sequence/test_merge.cpp diff --git a/include/exec/sequence/merge.hpp b/include/exec/sequence/merge.hpp new file mode 100644 index 000000000..1dde7fa24 --- /dev/null +++ b/include/exec/sequence/merge.hpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "../../stdexec/concepts.hpp" +#include "../../stdexec/execution.hpp" +#include "../sequence_senders.hpp" + +#include "../__detail/__basic_sequence.hpp" +#include "./transform_each.hpp" +#include "./ignore_all_values.hpp" +#include "stdexec/__detail/__execution_fwd.hpp" +#include "stdexec/__detail/__meta.hpp" +#include "stdexec/__detail/__senders_core.hpp" +#include "stdexec/__detail/__transform_completion_signatures.hpp" + +namespace exec { + namespace __merge { + using namespace stdexec; + + template + struct __operation_base { + _Receiver __receiver_; + }; + + template + struct __result_receiver { + using _Receiver = stdexec::__t<_ReceiverId>; + + struct __t { + using receiver_concept = stdexec::receiver_t; + using __id = __result_receiver; + + __operation_base<_Receiver>* __op_; + + void set_value() noexcept { + stdexec::set_value(static_cast<_Receiver&&>(__op_->__receiver_)); + } + + template + void set_error(_Error&& __error) noexcept { + stdexec::set_error( + static_cast<_Receiver&&>(__op_->__receiver_), static_cast<_Error&&>(__error)); + } + + void set_stopped() noexcept + { + stdexec::set_stopped(static_cast<_Receiver&&>(__op_->__receiver_)); + } + + auto get_env() const noexcept -> env_of_t<_Receiver> { + return stdexec::get_env(__op_->__receiver_); + } + }; + }; + + template + struct __merge_each_fn { + using _Receiver = stdexec::__t<_ReceiverId>; + + template + auto operator()(_Item&& __item, __operation_base<_Receiver>* __op) const noexcept( + __nothrow_callable) + -> next_sender_of_t<_Receiver, _Item> { + return exec::set_next( + __op->__receiver_, static_cast<_Item&&>(__item)); + } + }; + + struct __combine { + template + using merge_each_fn_t = __binder_back<__merge_each_fn<_ReceiverId>, __operation_base<__t<_ReceiverId>>*>; + + template + using transform_sender_t = __call_result_t>; + template + using ignored_sender_t = __call_result_t>; + + template + using result_sender_t = __call_result_t...>; + }; + + template + struct __operation { + using _Receiver = stdexec::__t<_ReceiverId>; + + using merge_each_fn_t = typename __combine::merge_each_fn_t<_ReceiverId>; + + template + using result_sender_t = typename __combine::result_sender_t<_ReceiverIdDependent, _Sequences...>; + + struct __t : __operation_base<_Receiver> { + using __id = __operation; + + connect_result_t, stdexec::__t<__result_receiver<_ReceiverId>>> __op_result_; + + __t(_Receiver __rcvr, _Sequences... __sequences) + : __operation_base< + _Receiver + >{static_cast<_Receiver&&>(__rcvr)} + , __op_result_{stdexec::connect( + stdexec::when_all( + exec::ignore_all_values( + exec::transform_each(static_cast<_Sequences&&>(__sequences), merge_each_fn_t{{this}, {}, {}}))...), + stdexec::__t<__result_receiver<_ReceiverId>>{this})} { + } + + void start() & noexcept { + stdexec::start(__op_result_); + } + }; + }; + + template + struct __subscribe_fn { + _Receiver& __rcvr_; + + template + auto operator()(__ignore, _Sequences... __sequences) noexcept( + (__nothrow_decay_copyable<_Sequences> && ...) + && __nothrow_move_constructible<_Receiver>) + -> __t<__operation<__id<_Receiver>, _Sequences...>> { + return { + static_cast<_Receiver&&>(__rcvr_), + static_cast<_Sequences&&>(__sequences)...}; + } + }; + + struct merge_t { + template + auto operator()(_Sequences&&... __sequences) const + noexcept((__nothrow_decay_copyable<_Sequences> && ...)) + -> __well_formed_sequence_sender auto { + auto __domain = __common_domain_t<_Sequences...>(); + return transform_sender( + __domain, make_sequence_expr( + static_cast<_Sequences&&>(__sequences)...)); + } + + template + using __all_nothrow_decay_copyable = __mbool<(__nothrow_decay_copyable<_Args> && ...)>; + + template + using __set_error_t = completion_signatures)>; + + struct _INVALID_ARGUMENTS_TO_MERGE_ { }; + + template + using __error_t = __mexception< + _INVALID_ARGUMENTS_TO_MERGE_, + __children_of<_Self, __q<_WITH_SEQUENCES_>>, + _WITH_ENVIRONMENT_<_Env> + >; + + template + struct __completions_t { + + template + using __f = __meval< + __concat_completion_signatures, + completion_signatures, + __sequence_completion_signatures_of_t<_Sequences, _Env...>... + >; + }; + + template + using __completions = __children_of<_Self, __completions_t<_Env...>>; + + template _Self, class... _Env> + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept { + return __minvoke<__mtry_catch<__q<__completions>, __q<__error_t>>, _Self, _Env...>(); + } + + template + struct __items_t { + + template + using __f = stdexec::__mapply< + stdexec::__munique>, + stdexec::__minvoke< + stdexec::__mconcat>, + __item_types_of_t<_Sequences, _Env...>...>>; + }; + + template + using __items = __children_of<_Self, __items_t<_Env...>>; + + template _Self, class... _Env> + static auto get_item_types(_Self&&, _Env&&...) noexcept { + return __minvoke<__mtry_catch<__q<__items>, __q<__error_t>>, _Self, _Env...>(); + } + + template _Self, receiver _Receiver> + static auto subscribe(_Self&& __self, _Receiver __rcvr) + noexcept(__nothrow_callable<__sexpr_apply_t, _Self, __subscribe_fn<_Receiver>>) + -> __sexpr_apply_result_t<_Self, __subscribe_fn<_Receiver>> { + return __sexpr_apply(static_cast<_Self&&>(__self), __subscribe_fn<_Receiver>{__rcvr}); + } + }; + } // namespace __merge + + using __merge::merge_t; + inline constexpr merge_t merge{}; +} // namespace exec diff --git a/test/exec/CMakeLists.txt b/test/exec/CMakeLists.txt index 50eff8e79..eebb67cdd 100644 --- a/test/exec/CMakeLists.txt +++ b/test/exec/CMakeLists.txt @@ -50,6 +50,7 @@ set(exec_test_sources sequence/test_ignore_all_values.cpp sequence/test_iterate.cpp sequence/test_transform_each.cpp + sequence/test_merge.cpp $<$:../execpools/test_tbb_thread_pool.cpp> $<$:../execpools/test_taskflow_thread_pool.cpp> $<$:../execpools/test_asio_thread_pool.cpp> diff --git a/test/exec/sequence/test_merge.cpp b/test/exec/sequence/test_merge.cpp new file mode 100644 index 000000000..2c832fd1b --- /dev/null +++ b/test/exec/sequence/test_merge.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "exec/sequence/merge.hpp" + +#include "exec/sequence/empty_sequence.hpp" +#include "exec/sequence/iterate.hpp" +#include "exec/sequence/ignore_all_values.hpp" +#include "exec/sequence/transform_each.hpp" +#include "exec/sequence.hpp" +#include "exec/sequence_senders.hpp" +#include "exec/trampoline_scheduler.hpp" +#include "exec/static_thread_pool.hpp" +#include "stdexec/__detail/__just.hpp" +#include "stdexec/__detail/__meta.hpp" +#include "stdexec/__detail/__continues_on.hpp" +#include "stdexec/__detail/__upon_error.hpp" +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace { + +struct null_receiver { + using __id = null_receiver; + using __t = null_receiver; + using receiver_concept = ex::receiver_t; + + void set_value() noexcept { + } + + template + void set_error(_Error&& ) noexcept { + } + + void set_stopped() noexcept { + } + + [[nodiscard]] + auto get_env() const noexcept -> ex::env<> { + return {}; + } + + struct ignore_values_fn_t { + template + void operator()(_Vs&&...) const noexcept {} + }; + + template + [[nodiscard]] + auto set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) + -> stdexec::__call_result_t, + ignore_values_fn_t> { + return stdexec::upon_error(stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); + } +}; + + TEST_CASE( + "merge - merge two sequence senders of no elements", + "[sequence_senders][merge][empty_sequence]") { + int counter = 0; + auto merged = exec::merge(exec::empty_sequence(), exec::empty_sequence()); + auto op = exec::subscribe(merged, null_receiver{}); + ex::start(op); + CHECK(counter == 0); + } + + TEST_CASE( + "merge - merge three sequence senders of no elements", + "[sequence_senders][merge][empty_sequence]") { + int counter = 0; + auto merged = exec::merge(exec::empty_sequence(), exec::empty_sequence(), exec::empty_sequence()); + auto op = exec::subscribe(merged, null_receiver{}); + ex::start(op); + CHECK(counter == 0); + } + + TEST_CASE( + "merge - merge sender of 2 senders", + "[sequence_senders][merge]") { + int value = 0; + int count = 0; + auto merged = exec::merge(ex::just(84), ex::just(-42)); + auto transformed = exec::transform_each(merged, ex::then([&value, &count](int x) noexcept { + value += x; + ++count; + })); + auto op = exec::subscribe(transformed, null_receiver{}); + ex::start(op); + CHECK(value == 42); + CHECK(count == 2); + } + + TEST_CASE( + "merge - merge sender of 2 senders and ignores all values", + "[sequence_senders][merge][ignore_all_values]") { + int value = 0; + int count = 0; + auto merged = exec::merge(ex::just(84), ex::just(-42)); + auto transformed = exec::transform_each(merged, ex::then([&value, &count](int x) { + value += x; + ++count; + return value; + })) + | exec::ignore_all_values(); + ex::sync_wait(transformed); + CHECK(value == 42); + CHECK(count == 2); + } + +#if STDEXEC_HAS_STD_RANGES() + TEST_CASE( + "merge - merge sender merges all items", + "[sequence_senders][merge][iterate]") { + auto range = [](auto from, auto to) { + return exec::iterate(std::views::iota(from, to)); + }; + auto then_each = [](auto f) { + return exec::transform_each(ex::then(f)); + }; + // this trampoline is used to interleave the merged iterate() sequences + // the parameters set the max inline schedule recursion depth and max + // inline schedule stack size + exec::trampoline_scheduler sched{16, 512}; + int total = 0; + int count = 0; + std::ptrdiff_t max = 0; + auto sum = exec::merge(range(100, 120), range(200, 220), range(300, 320)) + | then_each([&total, &count, &max](int x) noexcept { + std::ptrdiff_t current = 0; + current = std::abs(reinterpret_cast(¤t) - reinterpret_cast(&max)); + max = current > max ? current : max; + UNSCOPED_INFO("item: " << x << ", stack size: " << current); + total += x; + ++count; + }); + // this causes both iterate sequences to use the same trampoline. + ex::sync_wait(exec::sequence( + stdexec::schedule(sched), + exec::ignore_all_values(sum))); + UNSCOPED_INFO("max stack size: " << max); + CHECK(total == 12570); + CHECK(count == 60); + } + + TEST_CASE( + "merge - merge sender merges all items from multiple threads", + "[sequence_senders][static_thread_pool][merge][iterate]") { + + exec::static_thread_pool ctx0{1}; + ex::scheduler auto sched0 = ctx0.get_scheduler(); + exec::static_thread_pool ctx1{1}; + ex::scheduler auto sched1 = ctx1.get_scheduler(); + exec::static_thread_pool ctx2{1}; + ex::scheduler auto sched2 = ctx2.get_scheduler(); + exec::static_thread_pool ctx3{1}; + ex::scheduler auto sched3 = ctx3.get_scheduler(); + + auto range = [](auto from, auto to) { + return exec::iterate(std::views::iota(from, to)); + }; + auto then_each = [](auto f) { + return exec::transform_each(ex::then(f)); + }; + auto continues_each_on = [](auto sched) { + return exec::transform_each(ex::continues_on(sched)); + }; + int total = 0; + int count = 0; + auto sum = exec::merge( + range(100, 120) | continues_each_on(sched0), + range(200, 220) | continues_each_on(sched1), + range(300, 320) | continues_each_on(sched2)) + | then_each([](int x) noexcept { + // runs on sched0 and sched1 and sched2 in parallel. + // access to shared data would need to be protected + return std::make_tuple(x, std::this_thread::get_id()); + }) + | continues_each_on(sched3) + | then_each([&total, &count](auto v) { + // runs only on sched3, which is a strand (a static + // pool with one thread) + // it is safe to use shared data here + auto [x, id] = v; + total += x; + ++count; + UNSCOPED_INFO("item: " << x + << ", from thread id: " << id + << ", on thread id: " << std::this_thread::get_id()); + }); + ex::sync_wait(exec::sequence( + ex::schedule(sched3), + exec::ignore_all_values(sum))); + CHECK(total == 12570); + CHECK(count == 60); + } +#endif + + struct my_domain { + template Sender, class Env> + static auto transform_sender(Sender&&, const Env&) { + return ex::just(int{21}); + } + }; + + TEST_CASE("merge - can be customized late", "[merge][ignore_all_values]") { + // The customization will return a different value + basic_inline_scheduler sched; + int result = 0; + int count = 0; + auto start = ex::just(std::string{"hello"}); + auto with_scheduler = ex::write_env(ex::prop{ex::get_scheduler, inline_scheduler()}); + auto adaptor = ex::on(sched, ex::then([](std::string x) { return x + ", world"; })) + | with_scheduler; + auto snd = exec::merge( + start | exec::transform_each(adaptor), + start | exec::transform_each(adaptor)) + | exec::transform_each(ex::then([&](int x) { result += x; ++count; })) + | exec::ignore_all_values(); + ex::sync_wait(snd); + CHECK(result == 42); + CHECK(count == 2); + } + +} // namespace From ef85ae8e04f2cf22effd4d291deda46e5b606d96 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sun, 12 Oct 2025 14:18:20 +0000 Subject: [PATCH 07/39] fix bugs in merge and transform_each --- include/exec/sequence/merge.hpp | 6 +++--- include/exec/sequence/transform_each.hpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/exec/sequence/merge.hpp b/include/exec/sequence/merge.hpp index 1dde7fa24..e059243ad 100644 --- a/include/exec/sequence/merge.hpp +++ b/include/exec/sequence/merge.hpp @@ -72,7 +72,7 @@ namespace exec { struct __merge_each_fn { using _Receiver = stdexec::__t<_ReceiverId>; - template + template auto operator()(_Item&& __item, __operation_base<_Receiver>* __op) const noexcept( __nothrow_callable) -> next_sender_of_t<_Receiver, _Item> { @@ -131,7 +131,7 @@ namespace exec { _Receiver& __rcvr_; template - auto operator()(__ignore, _Sequences... __sequences) noexcept( + auto operator()(__ignore, __ignore, _Sequences... __sequences) noexcept( (__nothrow_decay_copyable<_Sequences> && ...) && __nothrow_move_constructible<_Receiver>) -> __t<__operation<__id<_Receiver>, _Sequences...>> { @@ -148,7 +148,7 @@ namespace exec { -> __well_formed_sequence_sender auto { auto __domain = __common_domain_t<_Sequences...>(); return transform_sender( - __domain, make_sequence_expr( + __domain, make_sequence_expr(__(), static_cast<_Sequences&&>(__sequences)...)); } diff --git a/include/exec/sequence/transform_each.hpp b/include/exec/sequence/transform_each.hpp index a09624c5d..cf8d0d743 100644 --- a/include/exec/sequence/transform_each.hpp +++ b/include/exec/sequence/transform_each.hpp @@ -124,18 +124,18 @@ namespace exec { struct __try_adaptor_calls_t { template - auto operator()(_Item*) -> stdexec::__mexception< + auto __try_adaptor_for_item(_Item*) -> stdexec::__mexception< _NOT_CALLABLE_ADAPTOR_<_Adaptor&>, _WITH_ITEM_SENDER_> >; template requires stdexec::__callable<_Adaptor&, _Item> - auto operator()(_Item*) -> stdexec::__msuccess; + auto __try_adaptor_for_item(_Item*) -> stdexec::__msuccess; template auto operator()(item_types<_Items...>*) -> decltype(( - stdexec::__msuccess(), ..., (*this)(static_cast<_Items*>(nullptr)))); + stdexec::__msuccess(), ..., __try_adaptor_for_item(static_cast<_Items*>(nullptr)))); }; template From 86c2afb0192754f4eeb9a5fd0b3207153300e5f6 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sun, 12 Oct 2025 19:00:34 +0000 Subject: [PATCH 08/39] apply PR feedback --- include/exec/sequence/merge.hpp | 15 +-- include/exec/sequence_senders.hpp | 189 +++++++++++++++--------------- 2 files changed, 98 insertions(+), 106 deletions(-) diff --git a/include/exec/sequence/merge.hpp b/include/exec/sequence/merge.hpp index e059243ad..01511e6ef 100644 --- a/include/exec/sequence/merge.hpp +++ b/include/exec/sequence/merge.hpp @@ -152,9 +152,6 @@ namespace exec { static_cast<_Sequences&&>(__sequences)...)); } - template - using __all_nothrow_decay_copyable = __mbool<(__nothrow_decay_copyable<_Args> && ...)>; - template using __set_error_t = completion_signatures)>; @@ -168,7 +165,7 @@ namespace exec { >; template - struct __completions_t { + struct __completions_fn_t { template using __f = __meval< @@ -179,15 +176,15 @@ namespace exec { }; template - using __completions = __children_of<_Self, __completions_t<_Env...>>; + using __completions_t = __children_of<_Self, __completions_fn_t<_Env...>>; template _Self, class... _Env> static auto get_completion_signatures(_Self&&, _Env&&...) noexcept { - return __minvoke<__mtry_catch<__q<__completions>, __q<__error_t>>, _Self, _Env...>(); + return __minvoke<__mtry_catch<__q<__completions_t>, __q<__error_t>>, _Self, _Env...>(); } template - struct __items_t { + struct __items_fn_t { template using __f = stdexec::__mapply< @@ -198,11 +195,11 @@ namespace exec { }; template - using __items = __children_of<_Self, __items_t<_Env...>>; + using __items_t = __children_of<_Self, __items_fn_t<_Env...>>; template _Self, class... _Env> static auto get_item_types(_Self&&, _Env&&...) noexcept { - return __minvoke<__mtry_catch<__q<__items>, __q<__error_t>>, _Self, _Env...>(); + return __minvoke<__mtry_catch<__q<__items_t>, __q<__error_t>>, _Self, _Env...>(); } template _Self, receiver _Receiver> diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 51d2b7e9b..06fbf9326 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -34,16 +34,16 @@ namespace exec { using __f = __mand<__mapply<__mcontains<_Needles>, _Haystack>...>; }; template - using __mall_contained_in = __mapply<__mall_contained_in_impl<_Haystack>, _Needles>; + using __mall_contained_in_t = __mapply<__mall_contained_in_impl<_Haystack>, _Needles>; template - concept __all_contained_in = __v<__mall_contained_in<_Needles, _Haystack>>; + concept __all_contained_in_t = __v<__mall_contained_in_t<_Needles, _Haystack>>; } // namespace __sequence_sndr // This concept checks if a given sender satisfies the requirements to be returned from `set_next`. template > concept next_sender = stdexec::sender_in<_Sender, _Env> - && __sequence_sndr::__all_contained_in< + && __sequence_sndr::__all_contained_in_t< stdexec::completion_signatures_of_t<_Sender, _Env>, stdexec::completion_signatures >; @@ -100,7 +100,7 @@ namespace exec { using receiver_concept = stdexec::receiver_t; using __id = __stopped_means_break; using _Receiver = stdexec::__t<_ReceiverId>; - using _Token = stop_token_of_t>; + using __token_t = stop_token_of_t>; STDEXEC_ATTRIBUTE(no_unique_address) _Receiver __rcvr_; auto get_env() const noexcept -> env_of_t<_Receiver> { @@ -115,9 +115,9 @@ namespace exec { void set_stopped() noexcept requires __callable - && (unstoppable_token<_Token> || __callable) + && (unstoppable_token<__token_t> || __callable) { - if constexpr (unstoppable_token<_Token>) { + if constexpr (unstoppable_token<__token_t>) { stdexec::set_value(static_cast<_Receiver&&>(__rcvr_)); } else { auto __token = stdexec::get_stop_token(stdexec::get_env(__rcvr_)); @@ -181,7 +181,7 @@ namespace exec { __call_result_t; template - using __unrecognized_sequence_error = + using __unrecognized_sequence_error_t = __mexception<_UNRECOGNIZED_SEQUENCE_TYPE_<>, _WITH_SEQUENCE_<_Sequence>, _WITH_ENVIRONMENT_<_Env>...>; template @@ -193,14 +193,14 @@ namespace exec { _Sequence)::get_item_types(__declval<_Sequence>(), __declval<_Env>())); template - using __tfx_sequence = + using __tfx_sequence_t = transform_sender_result_t<__late_domain_of_t<_Sequence, _Env>, _Sequence, _Env>; template - concept __with_tag_invoke = tag_invocable, _Env>; + concept __with_tag_invoke = tag_invocable, _Env>; template - using __member_alias_t = typename __decay_t<__tfx_sequence<_Sequence, _Env>>::item_types; + using __member_alias_t = typename __decay_t<__tfx_sequence_t<_Sequence, _Env>>::item_types; template concept __with_member_alias = __mvalid<__member_alias_t, _Sequence, _Env>; @@ -216,35 +216,35 @@ namespace exec { static auto __impl() { static_assert(sizeof(_Sequence), "Incomplete type used with get_item_types"); static_assert(sizeof(_Env), "Incomplete type used with get_item_types"); - using _TfxSequence = __tfx_sequence<_Sequence, _Env>; - if constexpr (__merror<_TfxSequence>) { + using __tfx_sequence_t = __tfx_sequence_t<_Sequence, _Env>; + if constexpr (__merror<__tfx_sequence_t>) { // Computing the type of the transformed sender returned an error type. Propagate it. - return static_cast<_TfxSequence (*)()>(nullptr); - } else if constexpr (__with_member_alias<_TfxSequence, _Env>) { - using _Result = __member_alias_t<_TfxSequence, _Env>; - return static_cast<_Result (*)()>(nullptr); - } else if constexpr (__with_static_member<_TfxSequence, _Env>) { - using _Result = __static_member_result_t<_TfxSequence, _Env>; - return static_cast<_Result (*)()>(nullptr); - } else if constexpr (__with_member<_TfxSequence, _Env>) { - using _Result = decltype(__declval<_TfxSequence>().get_item_types(__declval<_Env>())); - return static_cast<_Result (*)()>(nullptr); - } else if constexpr (__with_tag_invoke<_TfxSequence, _Env>) { - using _Result = tag_invoke_result_t; - return static_cast<_Result (*)()>(nullptr); + return static_cast<__tfx_sequence_t (*)()>(nullptr); + } else if constexpr (__with_member_alias<__tfx_sequence_t, _Env>) { + using __result_t = __member_alias_t<__tfx_sequence_t, _Env>; + return static_cast<__result_t (*)()>(nullptr); + } else if constexpr (__with_static_member<__tfx_sequence_t, _Env>) { + using __result_t = __static_member_result_t<__tfx_sequence_t, _Env>; + return static_cast<__result_t (*)()>(nullptr); + } else if constexpr (__with_member<__tfx_sequence_t, _Env>) { + using __result_t = decltype(__declval<__tfx_sequence_t>().get_item_types(__declval<_Env>())); + return static_cast<__result_t (*)()>(nullptr); + } else if constexpr (__with_tag_invoke<__tfx_sequence_t, _Env>) { + using __result_t = tag_invoke_result_t; + return static_cast<__result_t (*)()>(nullptr); } else if constexpr ( - sender_in<_TfxSequence, _Env> && !enable_sequence_sender>) { - using _Result = item_types>; - return static_cast<_Result (*)()>(nullptr); + sender_in<__tfx_sequence_t, _Env> && !enable_sequence_sender>) { + using __result_t = item_types>; + return static_cast<__result_t (*)()>(nullptr); } else if constexpr (__is_debug_env<_Env>) { using __tag_invoke::tag_invoke; // This ought to cause a hard error that indicates where the problem is. - using _ItemTypes - [[maybe_unused]] = tag_invoke_result_t; + using __item_types_t + [[maybe_unused]] = tag_invoke_result_t; return static_cast<__debug::__item_types (*)()>(nullptr); } else { - using _Result = __unrecognized_sequence_error<_Sequence, _Env>; - return static_cast<_Result (*)()>(nullptr); + using __result_t = __unrecognized_sequence_error_t<_Sequence, _Env>; + return static_cast<__result_t (*)()>(nullptr); } } @@ -382,14 +382,14 @@ namespace exec { >; template - using __item_completion_signatures = stdexec::transform_completion_signatures< + using __item_completion_signatures_t = stdexec::transform_completion_signatures< stdexec::__completion_signatures_of_t<_Sender, _Env...>, stdexec::completion_signatures, stdexec::__mconst>::__f >; template - using __sequence_completion_signatures = stdexec::transform_completion_signatures< + using __sequence_completion_signatures_t = stdexec::transform_completion_signatures< stdexec::__completion_signatures_of_t<_Sequence, _Env...>, stdexec::completion_signatures, stdexec::__mconst>::__f @@ -398,10 +398,10 @@ namespace exec { template using __sequence_completion_signatures_of_t = stdexec::__mapply< stdexec::__mtransform< - stdexec::__mbind_back_q<__item_completion_signatures, _Env...>, + stdexec::__mbind_back_q<__item_completion_signatures_t, _Env...>, stdexec::__mbind_back< stdexec::__mtry_q, - __sequence_completion_signatures<_Sequence, _Env...> + __sequence_completion_signatures_t<_Sequence, _Env...> > >, __item_types_of_t<_Sequence, _Env...> @@ -433,7 +433,7 @@ namespace exec { }; template - using __single_sender_completion_sigs = __if_c< + using __next_sender_completion_sigs_t = __if_c< unstoppable_token>, completion_signatures, completion_signatures @@ -476,61 +476,61 @@ namespace exec { struct subscribe_t { template - using __tfx_sequence = __tfx_sequence<_Sequence, env_of_t<_Receiver>>; + using __tfx_sequence_t = __tfx_sequence_t<_Sequence, env_of_t<_Receiver>>; template static constexpr auto __select_impl() noexcept { - using _Domain = __late_domain_of_t<_Sequence, env_of_t<_Receiver&>>; + using __domain_t = __late_domain_of_t<_Sequence, env_of_t<_Receiver&>>; constexpr bool _NothrowTfxSequence = - __nothrow_callable>; - using _TfxSequence = __tfx_sequence<_Sequence, _Receiver>; - if constexpr (__next_connectable<_TfxSequence, _Receiver>) { - using _Result = connect_result_t< - next_sender_of_t<_Receiver, _TfxSequence>, + __nothrow_callable>; + using __tfx_sequence_t = __tfx_sequence_t<_Sequence, _Receiver>; + if constexpr (__next_connectable<__tfx_sequence_t, _Receiver>) { + using __result_t = connect_result_t< + next_sender_of_t<_Receiver, __tfx_sequence_t>, __stopped_means_break_t<_Receiver> >; static_assert( - operation_state<_Result>, + operation_state<__result_t>, "stdexec::connect(sender, receiver) must return a type that " "satisfies the operation_state concept"); constexpr bool _Nothrow = __nothrow_connectable< - next_sender_of_t<_Receiver, _TfxSequence>, + next_sender_of_t<_Receiver, __tfx_sequence_t>, __stopped_means_break_t<_Receiver> >; - return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr); - } else if constexpr (__subscribable_with_static_member<_TfxSequence, _Receiver>) { - using _Result = decltype(STDEXEC_REMOVE_REFERENCE(_TfxSequence):: - subscribe(__declval<_TfxSequence>(), __declval<_Receiver>())); + return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); + } else if constexpr (__subscribable_with_static_member<__tfx_sequence_t, _Receiver>) { + using __result_t = decltype(STDEXEC_REMOVE_REFERENCE(__tfx_sequence_t):: + subscribe(__declval<__tfx_sequence_t>(), __declval<_Receiver>())); static_assert( - operation_state<_Result>, + operation_state<__result_t>, "Sequence::subscribe(sender, receiver) must return a type that " "satisfies the operation_state concept"); constexpr bool _Nothrow = _NothrowTfxSequence - && noexcept(STDEXEC_REMOVE_REFERENCE(_TfxSequence) - ::subscribe(__declval<_TfxSequence>(), __declval<_Receiver>())); - return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr); - } else if constexpr (__subscribable_with_member<_TfxSequence, _Receiver>) { - using _Result = decltype(__declval<_TfxSequence>().subscribe(__declval<_Receiver>())); + && noexcept(STDEXEC_REMOVE_REFERENCE(__tfx_sequence_t) + ::subscribe(__declval<__tfx_sequence_t>(), __declval<_Receiver>())); + return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); + } else if constexpr (__subscribable_with_member<__tfx_sequence_t, _Receiver>) { + using __result_t = decltype(__declval<__tfx_sequence_t>().subscribe(__declval<_Receiver>())); static_assert( - operation_state<_Result>, + operation_state<__result_t>, "Sequence::subscribe(sender, receiver) must return a type that " "satisfies the operation_state concept"); constexpr bool _Nothrow = _NothrowTfxSequence - && noexcept(__declval<_TfxSequence>() + && noexcept(__declval<__tfx_sequence_t>() .subscribe(__declval<_Receiver>())); - return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr); - } else if constexpr (__subscribable_with_tag_invoke<_TfxSequence, _Receiver>) { - using _Result = tag_invoke_result_t; + return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); + } else if constexpr (__subscribable_with_tag_invoke<__tfx_sequence_t, _Receiver>) { + using __result_t = tag_invoke_result_t; static_assert( - operation_state<_Result>, + operation_state<__result_t>, "exec::subscribe(sender, receiver) must return a type that " "satisfies the operation_state concept"); constexpr bool _Nothrow = _NothrowTfxSequence - && nothrow_tag_invocable; - return static_cast<_Result (*)() noexcept(_Nothrow)>(nullptr); + && nothrow_tag_invocable; + return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); } else if constexpr (__is_debug_env>) { - using _Result = __debug::__debug_operation; - return static_cast<_Result (*)() noexcept(_NothrowTfxSequence)>(nullptr); + using __result_t = __debug::__debug_operation; + return static_cast<__result_t (*)() noexcept(_NothrowTfxSequence)>(nullptr); } else { return _NO_USABLE_SUBSCRIBE_CUSTOMIZATION_FOUND_(); } @@ -540,53 +540,48 @@ namespace exec { using __select_impl_t = decltype(__select_impl<_Sequence, _Receiver>()); template - requires __next_connectable<__tfx_sequence<_Sequence, _Receiver>, _Receiver> - || __subscribable_with_static_member<__tfx_sequence<_Sequence, _Receiver>, _Receiver> - || __subscribable_with_member<__tfx_sequence<_Sequence, _Receiver>, _Receiver> - || __subscribable_with_tag_invoke<__tfx_sequence<_Sequence, _Receiver>, _Receiver> - || __is_debug_env> auto operator()(_Sequence&& __sequence, _Receiver&& __rcvr) const noexcept(__nothrow_callable<__select_impl_t<_Sequence, _Receiver>>) -> __call_result_t<__select_impl_t<_Sequence, _Receiver>> { - using _TfxSequence = __tfx_sequence<_Sequence, _Receiver>; + using __tfx_sequence_t = __tfx_sequence_t<_Sequence, _Receiver>; auto&& __env = stdexec::get_env(__rcvr); auto __domain = __get_late_domain(__sequence, __env); - if constexpr (__next_connectable<_TfxSequence, _Receiver>) { - next_sender_of_t<_Receiver, _TfxSequence> __next = set_next( + if constexpr (__next_connectable<__tfx_sequence_t, _Receiver>) { + next_sender_of_t<_Receiver, __tfx_sequence_t> __next = set_next( __rcvr, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); return stdexec::connect( - static_cast&&>(__next), + static_cast&&>(__next), __stopped_means_break_t<_Receiver>{static_cast<_Receiver&&>(__rcvr)}); // NOLINTNEXTLINE(bugprone-branch-clone) - } else if constexpr (__subscribable_with_static_member<_TfxSequence, _Receiver>) { + } else if constexpr (__subscribable_with_static_member<__tfx_sequence_t, _Receiver>) { auto&& __tfx_sequence = transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); return __tfx_sequence .subscribe( - static_cast<_TfxSequence&&>(__tfx_sequence), + static_cast<__tfx_sequence_t&&>(__tfx_sequence), static_cast<_Receiver&&>(__rcvr)); - } else if constexpr (__subscribable_with_member<_TfxSequence, _Receiver>) { + } else if constexpr (__subscribable_with_member<__tfx_sequence_t, _Receiver>) { return stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env) .subscribe(static_cast<_Receiver&&>(__rcvr)); - } else if constexpr (__subscribable_with_tag_invoke<_TfxSequence, _Receiver>) { + } else if constexpr (__subscribable_with_tag_invoke<__tfx_sequence_t, _Receiver>) { return stdexec::tag_invoke( subscribe_t{}, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env), static_cast<_Receiver&&>(__rcvr)); - } else if constexpr (enable_sequence_sender>) { + } else if constexpr (enable_sequence_sender>) { // This should generate an instantiate backtrace that contains useful // debugging information. auto&& __tfx_sequence = transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); return __tfx_sequence .subscribe( - static_cast<_TfxSequence&&>(__tfx_sequence), + static_cast<__tfx_sequence_t&&>(__tfx_sequence), static_cast<_Receiver&&>(__rcvr)); } else { // This should generate an instantiate backtrace that contains useful // debugging information. - next_sender_of_t<_Receiver, _TfxSequence> __next = set_next( + next_sender_of_t<_Receiver, __tfx_sequence_t> __next = set_next( __rcvr, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); return stdexec::connect( - static_cast&&>(__next), + static_cast&&>(__next), __stopped_means_break_t<_Receiver>{static_cast<_Receiver&&>(__rcvr)}); } } @@ -600,7 +595,7 @@ namespace exec { using subscribe_result_t = __call_result_t; } // namespace __sequence_sndr - using __sequence_sndr::__single_sender_completion_sigs; + using __sequence_sndr::__next_sender_completion_sigs_t; using __sequence_sndr::subscribe_t; inline constexpr subscribe_t subscribe{}; @@ -660,7 +655,7 @@ namespace exec { " template \n" \ " auto get_item_types(_Env&&...) -> exec::item_types<\n" \ " // This sequence produces void items...\n" \ - " stdexec::__call_result_t,\n" \ + " stdexec::__call_result_t>\n" \ " {\n" \ " return {};\n" \ " }\n" \ @@ -687,12 +682,12 @@ namespace exec { std::constructible_from, _Sequence>, "The sequence cannot be decay-copied. Did you forget a std::move?"); } else { - using _Items = __item_types_of_t<_Sequence, _Env...>; - if constexpr (stdexec::__same_as<_Items, __sequence_sndr::__unrecognized_sequence_error<_Sequence, _Env...>>) { - static_assert(stdexec::__mnever<_Items>, STDEXEC_ERROR_CANNOT_COMPUTE_COMPLETION_SIGNATURES); - } else if constexpr (stdexec::__merror<_Items>) { + using __items_t = __item_types_of_t<_Sequence, _Env...>; + if constexpr (stdexec::__same_as<__items_t, __sequence_sndr::__unrecognized_sequence_error_t<_Sequence, _Env...>>) { + static_assert(stdexec::__mnever<__items_t>, STDEXEC_ERROR_CANNOT_COMPUTE_COMPLETION_SIGNATURES); + } else if constexpr (stdexec::__merror<__items_t>) { static_assert( - !stdexec::__merror<_Items>, STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR); + !stdexec::__merror<__items_t>, STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR); } else if constexpr (!__well_formed_item_senders<_Sequence>) { static_assert( __well_formed_item_senders<_Sequence>, @@ -745,15 +740,15 @@ namespace exec { void __debug_sequence_sender(_Sequence&& __sequence, const _Env& = {}) { if constexpr (!__is_debug_env<_Env>) { if constexpr (sequence_sender_in<_Sequence, _Env>) { - using _Sigs = stdexec::__completion_signatures_of_t<_Sequence, __debug_env_t<_Env>>; - using _ItemTypes = __sequence_sndr::__item_types_of_t<_Sequence, __debug_env_t<_Env>>; - using _Receiver = __debug_sequence_sender_receiver, _Env, _Sigs, _ItemTypes>; - if constexpr (!std::same_as<_Sigs, __debug::__completion_signatures> || !std::same_as<_ItemTypes, __debug::__item_types>) { - using _Operation = exec::subscribe_result_t<_Sequence, _Receiver>; + using __sigs_t = stdexec::__completion_signatures_of_t<_Sequence, __debug_env_t<_Env>>; + using __item_types_t = __sequence_sndr::__item_types_of_t<_Sequence, __debug_env_t<_Env>>; + using __receiver_t = __debug_sequence_sender_receiver, _Env, __sigs_t, __item_types_t>; + if constexpr (!std::same_as<__sigs_t, __debug::__completion_signatures> || !std::same_as<__item_types_t, __debug::__item_types>) { + using __operation_t = exec::subscribe_result_t<_Sequence, __receiver_t>; //static_assert(receiver_of<_Receiver, _Sigs>); - if constexpr (!std::same_as<_Operation, __debug_operation>) { + if constexpr (!std::same_as<__operation_t, __debug_operation>) { if (sizeof(_Sequence) == ~0ul) { // never true - auto __op = subscribe(static_cast<_Sequence&&>(__sequence), _Receiver{}); + auto __op = subscribe(static_cast<_Sequence&&>(__sequence), __receiver_t{}); stdexec::start(__op); } } From b3ca212d008f4b1c966583ed1adea9500f7d2fc9 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sun, 12 Oct 2025 14:00:05 +0000 Subject: [PATCH 09/39] remove requires clause that was preventing diagnosis of failed type checking in connect --- include/stdexec/__detail/__senders.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/stdexec/__detail/__senders.hpp b/include/stdexec/__detail/__senders.hpp index fb47b60bc..febeab5d9 100644 --- a/include/stdexec/__detail/__senders.hpp +++ b/include/stdexec/__detail/__senders.hpp @@ -192,7 +192,6 @@ namespace stdexec { struct connect_t { template - requires sender_in<_Sender, env_of_t<_Receiver>> && __receiver_from<_Receiver, _Sender> STDEXEC_ATTRIBUTE(always_inline) static constexpr auto __type_check_arguments() -> bool { if constexpr (sender_in<_Sender, env_of_t<_Receiver>>) { From bb09c373de6210d08214283d1561bd3f718ff59d Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sun, 12 Oct 2025 14:15:56 +0000 Subject: [PATCH 10/39] diagnostics improvements for sequence sender --- include/exec/sequence_senders.hpp | 232 +++++++++++++------- include/stdexec/__detail/__senders_core.hpp | 2 +- test/exec/test_sequence_senders.cpp | 2 +- 3 files changed, 151 insertions(+), 85 deletions(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 06fbf9326..ae0775490 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -19,8 +19,15 @@ #include "../stdexec/execution.hpp" #include "stdexec/__detail/__concepts.hpp" #include "stdexec/__detail/__meta.hpp" +#include "stdexec/__detail/__diagnostics.hpp" namespace exec { + template + struct _WITH_SEQUENCE_; + + template + struct _WITH_SEQUENCES_; + struct sequence_sender_t : stdexec::sender_t { }; using sequence_tag [[deprecated("Renamed to exec::sequence_sender_t")]] = exec::sequence_sender_t; @@ -37,13 +44,13 @@ namespace exec { using __mall_contained_in_t = __mapply<__mall_contained_in_impl<_Haystack>, _Needles>; template - concept __all_contained_in_t = __v<__mall_contained_in_t<_Needles, _Haystack>>; + concept __all_contained_in = __v<__mall_contained_in_t<_Needles, _Haystack>>; } // namespace __sequence_sndr // This concept checks if a given sender satisfies the requirements to be returned from `set_next`. template > concept next_sender = stdexec::sender_in<_Sender, _Env> - && __sequence_sndr::__all_contained_in_t< + && __sequence_sndr::__all_contained_in< stdexec::completion_signatures_of_t<_Sender, _Env>, stdexec::completion_signatures >; @@ -58,7 +65,7 @@ namespace exec { // This is a sequence-receiver CPO that is used to apply algorithms on an input sender and it // returns a next-sender. `set_next` is usually called in a context where a sender will be // connected to a receiver. Since calling `set_next` usually involves constructing senders it - // is allowed to throw an excpetion, which needs to be handled by a calling sequence-operation. + // is allowed to throw an exception, which needs to be handled by a calling sequence-operation. // The returned object is a sender that can complete with `set_value_t()` or `set_stopped_t()`. struct set_next_t { template @@ -162,12 +169,6 @@ namespace exec { "because the attempt to compute the item types failed."_mstr; } // namespace __errs - template - struct _WITH_SEQUENCE_; - - template - struct _WITH_SEQUENCES_; - template struct _UNRECOGNIZED_SEQUENCE_TYPE_; @@ -259,23 +260,20 @@ namespace exec { using __sequence_sndr::get_item_types_t; inline constexpr get_item_types_t get_item_types{}; - template - concept sequence_sender = stdexec::sender_in<_Sequence, _Env...> - && enable_sequence_sender>; - - template - concept has_sequence_item_types = requires(_Sequence&& __sequence, _Env&&... __env) { - { get_item_types(static_cast<_Sequence &&>(__sequence), static_cast<_Env &&>(__env)...) }; - }; - - template - concept sequence_sender_in = sequence_sender<_Sequence, _Env...> - && has_sequence_item_types<_Sequence, _Env...>; - template using __item_types_of_t = decltype(get_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); + template + concept has_sequence_item_types = + stdexec::sender_in<_Sequence, _Env...> + && requires(_Sequence&& __sequence, _Env&&... __env) { + { get_item_types(static_cast<_Sequence &&>(__sequence), static_cast<_Env &&>(__env)...) }; + }; + + template + concept sequence_sender = stdexec::sender_in<_Sequence, _Env...> + && enable_sequence_sender>; template struct _SEQUENCE_ITEM_IS_NOT_A_WELL_FORMED_SENDER_ { }; @@ -310,6 +308,11 @@ namespace exec { ..., exec::__check_item<_Sequence>(static_cast<_Items*>(nullptr)))); + template + concept __well_formed_item_types = requires(stdexec::__decay_t<_ItemTypes>* __item_types) { + { exec::__check_items<_Sequence>(__item_types) } -> stdexec::__ok; + }; + template requires stdexec::__merror<_Sequence> auto __check_sequence(_Sequence*) -> _Sequence; @@ -331,8 +334,7 @@ namespace exec { exec::__check_items<_Sequence>(static_cast<__item_types_of_t<_Sequence>*>(nullptr))); template - concept __well_formed_item_senders = has_sequence_item_types> - && requires(stdexec::__decay_t<_Sequence>* __sequence) { + concept __well_formed_item_senders = requires(stdexec::__decay_t<_Sequence>* __sequence) { { exec::__check_sequence(__sequence) } -> stdexec::__ok; }; @@ -341,6 +343,44 @@ namespace exec { && enable_sequence_sender> && __well_formed_item_senders<_Sequence>; + template + concept sequence_sender_in = sequence_sender<_Sequence, _Env...> + && requires(_Sequence&& __sequence, _Env&&... __env) { + { get_item_types(static_cast<_Sequence &&>(__sequence), static_cast<_Env &&>(__env)...) } + -> __well_formed_item_types<_Sequence>; + }; + + namespace __debug { + template , class _Sequence> + void __debug_sequence_sender(_Sequence&& __sequence, const _Env& = {}); + } // namespace __debug + using __debug::__debug_sequence_sender; + + template + static constexpr auto __diagnose_sequence_sender_concept_failure(); + + #if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() + // __checked_completion_signatures is for catching logic bugs in a sender's metadata. If sender + // and sender_in are both true, then they had better report the same metadata. This + // completion signatures wrapper enforces that at compile time. + template + auto __checked_item_types(_Sequence && __sequence, _Env &&... __env) noexcept { + using __item_types_t = __item_types_of_t<_Sequence, _Env...>; + static_assert(stdexec::__ok<__item_types_t>, "get_item_types returned an error"); + exec::__debug_sequence_sender(static_cast<_Sequence &&>(__sequence), __env...); + return __item_types_t{}; + } + + template + requires sequence_sender<_Sequence, _Env...> + using item_types_of_t = + decltype(exec::__checked_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); +#else + template + requires sequence_sender_in<_Sequence, _Env...> + using item_types_of_t = __item_types_of_t<_Sequence, _Env...>; +#endif + template struct _WITH_RECEIVER_ { }; @@ -447,43 +487,56 @@ namespace exec { && sender_to, __stopped_means_break_t<_Receiver>>; template - concept __subscribable_with_static_member = receiver<_Receiver> - && sequence_sender_in<_Sequence, env_of_t<_Receiver>> - && sequence_receiver_from<_Receiver, _Sequence> - && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { - { - STDEXEC_REMOVE_REFERENCE(_Sequence) - ::subscribe(static_cast<_Sequence &&>(__sequence), static_cast<_Receiver &&>(__rcvr)) - }; - }; + using __subscribe_member_result_t = decltype(__declval<_Sequence>().subscribe(__declval<_Receiver>())); + + template + using __subscribe_static_member_result_t = decltype(STDEXEC_REMOVE_REFERENCE( + _Sequence)::subscribe(__declval<_Sequence>(), __declval<_Receiver>())); template - concept __subscribable_with_member = receiver<_Receiver> - && sequence_sender_in<_Sequence, env_of_t<_Receiver>> - && sequence_receiver_from<_Receiver, _Sequence> - && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { - { - static_cast<_Sequence &&>(__sequence) - .subscribe(static_cast<_Receiver &&>(__rcvr)) - }; - }; + concept __subscribable_with_member = __mvalid<__subscribe_member_result_t, _Sequence, _Receiver>; template - concept __subscribable_with_tag_invoke = receiver<_Receiver> - && sequence_sender_in<_Sequence, env_of_t<_Receiver>> - && sequence_receiver_from<_Receiver, _Sequence> - && tag_invocable; + concept __subscribable_with_static_member = __mvalid<__subscribe_static_member_result_t, _Sequence, _Receiver>; + + template + concept __subscribable_with_tag_invoke = tag_invocable; struct subscribe_t { + template + STDEXEC_ATTRIBUTE(always_inline) + static constexpr auto __type_check_arguments() -> bool { + if constexpr (sequence_sender_in<_Sequence, env_of_t<_Receiver>>) { + // Instantiate __debug_sender via completion_signatures_of_t and + // item_types_of_t to check that the actual completions and item_types + // match the expected completions and values. + using __checked_signatures + [[maybe_unused]] = completion_signatures_of_t<_Sequence, env_of_t<_Receiver>>; + using __checked_item_types + [[maybe_unused]] = item_types_of_t<_Sequence, env_of_t<_Receiver>>; + } else { + __diagnose_sequence_sender_concept_failure<_Sequence, env_of_t<_Receiver>>(); + } + return true; + } + template using __tfx_sequence_t = __tfx_sequence_t<_Sequence, env_of_t<_Receiver>>; template static constexpr auto __select_impl() noexcept { - using __domain_t = __late_domain_of_t<_Sequence, env_of_t<_Receiver&>>; + using __domain_t = __late_domain_of_t<_Sequence, env_of_t<_Receiver>>; constexpr bool _NothrowTfxSequence = - __nothrow_callable>; + __nothrow_callable>; using __tfx_sequence_t = __tfx_sequence_t<_Sequence, _Receiver>; + + static_assert(sequence_sender<_Sequence> || has_sequence_item_types<_Sequence, env_of_t<_Receiver>>, "The first argument to stdexec::subscribe must be a sequence sender"); + static_assert( + receiver<_Receiver>, "The second argument to stdexec::subscribe must be a receiver"); +#if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() + static_assert(__type_check_arguments<__tfx_sequence_t, _Receiver>()); +#endif + if constexpr (__next_connectable<__tfx_sequence_t, _Receiver>) { using __result_t = connect_result_t< next_sender_of_t<_Receiver, __tfx_sequence_t>, @@ -547,6 +600,8 @@ namespace exec { auto&& __env = stdexec::get_env(__rcvr); auto __domain = __get_late_domain(__sequence, __env); if constexpr (__next_connectable<__tfx_sequence_t, _Receiver>) { + // sender as sequence of one + next_sender_of_t<_Receiver, __tfx_sequence_t> __next = set_next( __rcvr, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); return stdexec::connect( @@ -568,14 +623,18 @@ namespace exec { stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env), static_cast<_Receiver&&>(__rcvr)); } else if constexpr (enable_sequence_sender>) { + // sequence sender fallback + // This should generate an instantiate backtrace that contains useful // debugging information. - auto&& __tfx_sequence = transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); + auto&& __tfx_sequence = stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); return __tfx_sequence .subscribe( static_cast<__tfx_sequence_t&&>(__tfx_sequence), static_cast<_Receiver&&>(__rcvr)); - } else { + } else { + // sender as sequence of one fallback + // This should generate an instantiate backtrace that contains useful // debugging information. next_sender_of_t<_Receiver, __tfx_sequence_t> __next = set_next( @@ -631,13 +690,44 @@ namespace exec { } } - //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +#define STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE \ + "\n" \ + "\n" \ + "The given type is not a sequence sender because stdexec::enable_sequence_sender\n" \ + "is false. Either:\n" \ + "\n" \ + "1. Give the type a nested `::sender_concept` type that is an alias for `stdexec::sender_t`,\n" \ + " as in:\n" \ + "\n" \ + " class MySequence\n" \ + " {\n" \ + " public:\n" \ + " using sender_concept = exec::sequence_sender_t;\n" \ + " ...\n" \ + " };\n" \ + "\n" \ + " or,\n" \ + "\n" \ + "2. Specialize the `stdexec::enable_sequence_sender` boolean trait for this type to true,\n" \ + "as follows:\n" \ + "\n" \ + " class MySequence\n" \ + " {\n" \ + " ...\n" \ + " };\n" \ + "\n" \ + " template <>\n" \ + " inline constexpr bool stdexec::enable_sequence_sender = true;\n" + +//////////////////////////////////////////////////////////////////////////////// # define STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR \ "\n" \ "\n" \ "Trying to compute the sequences's item types resulted in an error. See\n" \ "the rest of the compiler diagnostic for clues. Look for the string \"_ERROR_\".\n" +//////////////////////////////////////////////////////////////////////////////// # define STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE \ "\n" \ "\n" \ @@ -662,12 +752,12 @@ namespace exec { " ...\n" \ " };\n" - // Used to report a meaningful error message when the sender_in + // Used to report a meaningful error message when the sender_in // concept check fails. template - auto __diagnose_sequence_concept_failure() { - if constexpr (!enable_sequence_sender>) { - static_assert(enable_sequence_sender<_Sequence>, STDEXEC_ERROR_ENABLE_SENDER_IS_FALSE); + static constexpr auto __diagnose_sequence_sender_concept_failure() { + if constexpr (!enable_sequence_sender> && !has_sequence_item_types, _Env...>) { + static_assert(enable_sequence_sender<_Sequence>, STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE); } else if constexpr (!stdexec::__detail::__consistent_completion_domains<_Sequence>) { static_assert( stdexec::__detail::__consistent_completion_domains<_Sequence>, @@ -736,8 +826,8 @@ namespace exec { } }; - template , class _Sequence> - void __debug_sequence_sender(_Sequence&& __sequence, const _Env& = {}) { + template + void __debug_sequence_sender(_Sequence&& __sequence, const _Env&) { if constexpr (!__is_debug_env<_Env>) { if constexpr (sequence_sender_in<_Sequence, _Env>) { using __sigs_t = stdexec::__completion_signatures_of_t<_Sequence, __debug_env_t<_Env>>; @@ -745,7 +835,7 @@ namespace exec { using __receiver_t = __debug_sequence_sender_receiver, _Env, __sigs_t, __item_types_t>; if constexpr (!std::same_as<__sigs_t, __debug::__completion_signatures> || !std::same_as<__item_types_t, __debug::__item_types>) { using __operation_t = exec::subscribe_result_t<_Sequence, __receiver_t>; - //static_assert(receiver_of<_Receiver, _Sigs>); + //static_assert(receiver_of<__receiver_t, __sigs_t>); if constexpr (!std::same_as<__operation_t, __debug_operation>) { if (sizeof(_Sequence) == ~0ul) { // never true auto __op = subscribe(static_cast<_Sequence&&>(__sequence), __receiver_t{}); @@ -754,34 +844,10 @@ namespace exec { } } } else { - __diagnose_sequence_concept_failure<_Sequence, _Env>(); + __diagnose_sequence_sender_concept_failure<_Sequence, _Env>(); } } } } // namespace __debug - using __debug::__debug_sequence_sender; - #if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() - // __checked_completion_signatures is for catching logic bugs in a sender's metadata. If sender - // and sender_in are both true, then they had better report the same metadata. This - // completion signatures wrapper enforces that at compile time. - template - auto __checked_item_types(_Sequence && __sequence, _Env &&... __env) noexcept { - using __completions_t = - decltype(get_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); - // (void)__sequence; - // [](auto&&...){}(__env...); - exec::__debug_sequence_sender(static_cast<_Sequence &&>(__sequence), __env...); - return __completions_t{}; - } - - template - requires sequence_sender_in<_Sequence, _Env...> - using item_types_of_t = - decltype(exec::__checked_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); -#else - template - requires sequence_sender_in<_Sequence, _Env...> - using item_types_of_t = __item_types_of_t<_Sequence, _Env...>; -#endif } // namespace exec diff --git a/include/stdexec/__detail/__senders_core.hpp b/include/stdexec/__detail/__senders_core.hpp index d6943f11c..214dbd936 100644 --- a/include/stdexec/__detail/__senders_core.hpp +++ b/include/stdexec/__detail/__senders_core.hpp @@ -73,7 +73,7 @@ namespace stdexec { // Used to report a meaningful error message when the sender_in // concept check fails. template - auto __diagnose_sender_concept_failure() { + static constexpr auto __diagnose_sender_concept_failure() { if constexpr (!enable_sender<__decay_t<_Sender>>) { static_assert(enable_sender<_Sender>, STDEXEC_ERROR_ENABLE_SENDER_IS_FALSE); } else if constexpr (!__detail::__consistent_completion_domains<_Sender>) { diff --git a/test/exec/test_sequence_senders.cpp b/test/exec/test_sequence_senders.cpp index d29f0974a..009c4fbc8 100644 --- a/test/exec/test_sequence_senders.cpp +++ b/test/exec/test_sequence_senders.cpp @@ -133,7 +133,7 @@ namespace { using item_types = exec::item_types>; template - friend auto tag_invoke(subscribe_t, some_sequence_sender_of self, R&& rcvr) -> nop_operation; + friend auto tag_invoke(subscribe_t, some_sequence_sender_of , R&& ) -> nop_operation { return {}; } }; TEST_CASE("sequence_senders - Test for subscribe", "[sequence_senders]") { From 3f1ba21b0c8383ab5be59b36b81a9c25ead382cb Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sun, 12 Oct 2025 13:38:35 +0000 Subject: [PATCH 11/39] add merge_each sequence sender adaptor merge_each is a sequence adaptor that takes a sequence of nested sequences and merges all the nested values from all the nested sequences into a single output sequence. the first error encountered will trigger a stop request for all active operations. The error is stored and is emitted only after all the active operations have completed. If the error was emitted from an item, a new item is emitted at the end to deliver the stored error. any nested sequence or nested value that completes with set_stopped will not cause any other operations to be stopped. This allows individual nested sequences to be stopped without breaking the merge of the remaining sequences. --- include/exec/sequence/merge_each.hpp | 1187 +++++++++++++++++ include/exec/sequence_senders.hpp | 7 +- test/exec/CMakeLists.txt | 2 + test/exec/sequence/test_merge_each.cpp | 370 +++++ .../sequence/test_merge_each_threaded.cpp | 357 +++++ 5 files changed, 1921 insertions(+), 2 deletions(-) create mode 100644 include/exec/sequence/merge_each.hpp create mode 100644 test/exec/sequence/test_merge_each.cpp create mode 100644 test/exec/sequence/test_merge_each_threaded.cpp diff --git a/include/exec/sequence/merge_each.hpp b/include/exec/sequence/merge_each.hpp new file mode 100644 index 000000000..be54b880a --- /dev/null +++ b/include/exec/sequence/merge_each.hpp @@ -0,0 +1,1187 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include "../../stdexec/concepts.hpp" +#include "../../stdexec/execution.hpp" +#include "../sequence_senders.hpp" + +#include "../__detail/__basic_sequence.hpp" +#include "stdexec/__detail/__concepts.hpp" +#include "stdexec/__detail/__config.hpp" +#include "stdexec/__detail/__diagnostics.hpp" +#include "stdexec/__detail/__execution_fwd.hpp" +#include "stdexec/__detail/__meta.hpp" +#include "stdexec/__detail/__sender_introspection.hpp" +#include "stdexec/__detail/__senders_core.hpp" +#include "stdexec/__detail/__stop_token.hpp" +#include "stdexec/__detail/__transform_completion_signatures.hpp" +#include "stdexec/__detail/__unstoppable.hpp" +#include "stdexec/__detail/__variant.hpp" + +#include + +namespace exec { + namespace __merge_each { + using namespace stdexec; + + struct __env_with_inplace_stop_token_t { + auto operator()(inplace_stop_token& __stop_token) const noexcept { + return stdexec::prop{stdexec::get_stop_token, __stop_token}; + } + template + auto operator()(stdexec::inplace_stop_token& __stop_token, _Env&& __env) const noexcept { + return __env::__join((*this)(__stop_token), static_cast<_Env&&>(__env)); + } + auto operator()(inplace_stop_source& __stop_source) const noexcept { + return stdexec::prop{stdexec::get_stop_token, __stop_source.get_token()}; + } + template + auto operator()(stdexec::inplace_stop_source& __stop_source, _Env&& __env) const noexcept { + return __env::__join((*this)(__stop_source), static_cast<_Env&&>(__env)); + } + }; + static constexpr inline __env_with_inplace_stop_token_t __env_with_inplace_stop_token; + + template + using __env_with_inplace_stop_token_result_t = decltype( + __env_with_inplace_stop_token( + stdexec::__declval(), + stdexec::__declval<_Env>()...) + ); + + template + struct __nested_stop { + + struct __on_stop_request { + inplace_stop_source& __stop_source_; + + void operator()() noexcept { + __stop_source_.request_stop(); + } + }; + using __env_t = env_of_t<_Receiver>; + using __env_result_t = __env_with_inplace_stop_token_result_t<__env_t>; + using __stop_token_t = stop_token_of_t<__env_t>; + using __stop_callback_t = stop_callback_for_t<__stop_token_t, __on_stop_request>; + struct no_callback {}; + using __callback_t = __if_c, no_callback, __optional<__stop_callback_t>>; + + inplace_stop_source __stop_source_{}; + __callback_t __on_stop_{}; + + void register_token(_Receiver& __receiver) noexcept { + if constexpr (!unstoppable_token<__stop_token_t>) { + // register stop callback: + __on_stop_.emplace( + get_stop_token(stdexec::get_env(__receiver)), __on_stop_request{__stop_source_}); + } + } + + void unregister_token() noexcept { + if constexpr (!unstoppable_token<__stop_token_t>) { + __on_stop_.reset(); + } + } + + bool stop_requested() const noexcept { + if constexpr (!unstoppable_token<__stop_token_t>) { + return __stop_source_.stop_requested(); + } + return false; + } + + bool request_stop() noexcept { + if constexpr (!unstoppable_token<__stop_token_t>) { + return __stop_source_.request_stop(); + } + return false; + } + + inplace_stop_token get_token() const& noexcept { + return __stop_source_.get_token(); + } + + static auto __env_from(__nested_stop* __self, __env_t&& __env) noexcept -> __env_result_t { + return __env_with_inplace_stop_token(__self->__stop_source_, static_cast<__env_t&&>(__env)); + } + auto env_from(__env_t&& __env) noexcept { + return __env_from(this, static_cast<__env_t&&>(__env)); + } + auto env_from(_Receiver& __receiver) noexcept { + return env_from(get_env(__receiver)); + } + + using env_t = + decltype(__env_from(std::declval<__nested_stop*>(), __declval<__env_t>())); + }; + + template + using drop = __types<>; + + enum class __completion_t { + __started, + __error, + __stopped + }; + + // + // __operation.. coordinates all the nested operation completions, creates + // a nested inplace_stop_source and stores the first error to arrive and + // delays the error to be emmitted after all nested operations have completed + // + // The first error to arrive will request_stop on the nested inplace_stop_source + // + + template + struct __operation_base_interface { + ~__operation_base_interface(){} + virtual void nested_value_started() noexcept = 0; + virtual void nested_value_complete() noexcept = 0; + virtual bool nested_value_fail() noexcept = 0; + virtual void nested_value_break() noexcept = 0; + virtual void error_complete() noexcept = 0; + + _ErrorStorage* __error_storage_; + inplace_stop_token __token_; + + __operation_base_interface(_ErrorStorage* __error_storage) noexcept + : __error_storage_{__error_storage} + , __token_{} {} + + template + auto env_from(_Env&& __env) noexcept -> __env_with_inplace_stop_token_result_t<_Env> { + return __env_with_inplace_stop_token(__token_, static_cast<_Env&&>(__env)); + } + + template + requires (!same_as<_Error, _ErrorStorage>) + void store_error(_Error&& __error) + noexcept( + __nothrow_callable), _ErrorStorage&, _Error>) { + if (this->nested_value_fail()) { + // We are the first child to complete with an error, so we must save the error. (Any + // subsequent errors are ignored.) + if constexpr (noexcept(__error_storage_->template emplace<_Error>(static_cast<_Error&&>(__error)))) { + __error_storage_->template emplace<_Error>(static_cast<_Error&&>(__error)); + } else { + STDEXEC_TRY { + __error_storage_->template emplace<_Error>(static_cast<_Error&&>(__error)); + } + STDEXEC_CATCH_ALL { + __error_storage_->template emplace(std::current_exception()); + } + } + } + } + }; + + // + // __error_.. provides the delayed error from a nested sequence + // or nested value as the final item in the merged output sequence + // + + template + struct __error_op { + using _ErrorReceiver = stdexec::__t<_ErrorReceiverId>; + using __operation_base_interface_t = __operation_base_interface<_ErrorStorage>; + + struct __t { + using __id = __error_op; + + _ErrorReceiver __receiver_; + __operation_base_interface_t* __op_; + + void start() & noexcept { + // emit delayed error into the sequence + __op_->__error_storage_->visit( + [this](auto&& __error) noexcept { + stdexec::set_error( + static_cast<_ErrorReceiver&&>(__receiver_), + static_cast(__error)); + } + , static_cast<_ErrorStorage&&>(*__op_->__error_storage_)); + } + }; + }; + + template + struct __error_sender { + using __operation_base_interface_t = __operation_base_interface<_ErrorStorage>; + struct __t { + using __id = __error_sender; + using sender_concept = stdexec::sender_t; + + template + using __error_op_t = stdexec::__t<__error_op<_ErrorReceiverId, _ErrorStorage>>; + + template + using __error_signature_t = stdexec::set_error_t(_Error); + + __operation_base_interface_t* __op_; + + template _Self, class... _Env> + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept + -> stdexec::__mapply< + stdexec::__mtransform< + stdexec::__q<__error_signature_t>, + stdexec::__qq>, + _ErrorStorage> { + return {}; + } + + template _Self, receiver _ErrorReceiver> + static auto connect(_Self&& __self, _ErrorReceiver&& __rcvr) + noexcept(__nothrow_move_constructible<_ErrorReceiver>) + -> __error_op_t> { + return {static_cast<_ErrorReceiver&&>(__rcvr), + __self.__op_}; + } + }; + }; + + template + struct __error_next_receiver { + using __t = __error_next_receiver; + using __id = __error_next_receiver; + using receiver_concept = stdexec::receiver_t; + + using __operation_base_interface_t = __operation_base_interface<_ErrorStorage>; + + __operation_base_interface_t* __op_; + _EnvFn __env_fn_; + + void set_value() noexcept { + __op_->error_complete(); + } + + void set_stopped() noexcept { + __op_->error_complete(); + } + + auto get_env() const noexcept -> __call_result_t<_EnvFn> { + return __env_fn_(); + } + }; + + template + struct __env_fn { + using __nested_stop_t = __nested_stop<_Receiver>; + using __nested_stop_env_t = typename __nested_stop_t::env_t; + + _Receiver* __receiver_; + __nested_stop_t* __source_; + + auto operator()() const noexcept + -> __nested_stop_env_t { + return __source_->env_from(*__receiver_); + } + }; + + template + struct __operation_base : __operation_base_interface<_ErrorStorage> { + using __nested_stop_t = __nested_stop<_Receiver>; + using __nested_stop_env_t = typename __nested_stop_t::env_t; + + using __error_storage_t = _ErrorStorage; + using __interface_t = __operation_base_interface<__error_storage_t>; + + using __error_sender_t = __t<__error_sender<__error_storage_t>>; + using __error_next_sender_t = next_sender_of_t<_Receiver&, __error_sender_t>; + using __env_fn_t = __env_fn<_Receiver>; + using __error_next_receiver_t = __error_next_receiver<_ErrorStorage, __env_fn_t>; + using __error_op_t = stdexec::connect_result_t<__error_next_sender_t, __error_next_receiver_t>; + + _Receiver __receiver_; + _ErrorStorage __error_storage_{}; + std::exception_ptr __ex_ = nullptr; + std::atomic_int32_t __active_ = 0; + std::atomic<__completion_t> __completion_{__completion_t::__started}; + __nested_stop_t __nested_stop_{}; + stdexec::__optional<__error_op_t> __error_op_{}; + + __operation_base(_Receiver __receiver) + noexcept(__nothrow_move_constructible<_Receiver>) + : __interface_t{&__error_storage_} + , __receiver_{static_cast<_Receiver&&>(__receiver)} { + __interface_t::__token_ = __nested_stop_.get_token(); + } + + template + void set_exception(_Error&& __error) noexcept { + switch (__completion_.exchange(__completion_t::__error)) { + case __completion_t::__started: + // We must request stop. When the previous state is __error or __stopped, then stop has + // already been requested. + __nested_stop_.request_stop(); + [[fallthrough]]; + case __completion_t::__stopped: + // We are the first child to complete with an error, so we must save the error. (Any + // subsequent errors are ignored.) + if constexpr (__nothrow_decay_copyable<_Error>) { + __ex_ = std::make_exception_ptr(static_cast<_Error&&>(__error)); + } else { + STDEXEC_TRY { + __ex_ = std::make_exception_ptr(static_cast<_Error&&>(__error)); + } + STDEXEC_CATCH_ALL { + __ex_ = std::current_exception(); + } + } + break; + case __completion_t::__error:; // We're already in the "error" state. Ignore the error. + } + } + void set_break() noexcept { + switch (__completion_.exchange(__completion_t::__stopped)) { + case __completion_t::__started: + // We must request stop. When the previous state is __error or __stopped, then stop has + // already been requested. + __nested_stop_.request_stop(); + break; + case __completion_t::__stopped: [[fallthrough]]; // We're already in the "stopped" state. Ignore the break. + case __completion_t::__error:; // We're already in the "error" state. Ignore the break. + } + } + + void sequence_started() noexcept { + __active_ = 1; + } + void sequence_complete() noexcept { + complete_if_none_active(); + } + void sequence_break() noexcept { + set_break(); + complete_if_none_active(); + } + void nested_sequence_started() noexcept { + ++__active_; + } + void nested_sequence_complete() noexcept { + complete_if_none_active(); + } + void nested_sequence_break() noexcept { + set_break(); + complete_if_none_active(); + } + void nested_value_started() noexcept override { + ++__active_; + } + void nested_value_complete() noexcept override { + complete_if_none_active(); + } + bool nested_value_fail() noexcept override { + switch (__completion_.exchange(__completion_t::__error)) { + case __completion_t::__started: + // We must request stop. When the previous state is __error or __stopped, then stop has + // already been requested. + __nested_stop_.request_stop(); + [[fallthrough]]; + case __completion_t::__stopped: + // We are the first child to complete with an error, so we must save the error. (Any + // subsequent errors are ignored.) + return true; + break; + case __completion_t::__error:; // We're already in the "error" state. Ignore the error. + } + return false; + } + void nested_value_break() noexcept override { + set_break(); + complete_if_none_active(); + } + void error_complete() noexcept override { + // do not double report error + stdexec::set_stopped(static_cast<_Receiver&&>(__receiver_)); + } + + void complete_if_none_active() noexcept { + if (--__active_ == 0) { + __nested_stop_.unregister_token(); + switch (__completion_.load(std::memory_order_relaxed)) { + case __completion_t::__started: + stdexec::set_value(static_cast<_Receiver&&>(__receiver_)); + break; + case __completion_t::__error: + if (__ex_ != nullptr) { + // forward error from the subscribed sequence of sequences + stdexec::set_error(static_cast<_Receiver&&>(__receiver_), static_cast(__ex_)); + } else { + // forward error from the nested sequences as the last item + if constexpr ( + __nothrow_callable + && __nothrow_connectable<__error_next_sender_t, __error_next_receiver_t>) { + auto __next_sender = exec::set_next(__receiver_, __error_sender_t{this}); + auto __next_receiver = __error_next_receiver_t{this, __env_fn{&__receiver_, &__nested_stop_}}; + __error_op_.__emplace_from([&]() { + return stdexec::connect( + static_cast<__error_next_sender_t&&>(__next_sender), + static_cast<__error_next_receiver_t&&>(__next_receiver)); + }); + stdexec::start(__error_op_.value()); + } else { + STDEXEC_TRY { + auto __next_sender = exec::set_next(__receiver_, __error_sender_t{this}); + auto __next_receiver = __error_next_receiver_t{this, __env_fn{&__receiver_, &__nested_stop_}}; + __error_op_.__emplace_from([&]() { + return stdexec::connect( + static_cast<__error_next_sender_t&&>(__next_sender), + static_cast<__error_next_receiver_t&&>(__next_receiver)); + }); + stdexec::start(__error_op_.value()); + } + STDEXEC_CATCH_ALL { + stdexec::set_error(static_cast<_Receiver&&>(__receiver_), std::current_exception()); + } + } + } + break; + case __completion_t::__stopped: + stdexec::set_stopped(static_cast<_Receiver&&>(__receiver_)); + break; + }; + } + } + }; + + // + // __nested_value.. exists to store the an error signal from a + // value if it is the first error. All error completions are + // removed from the completion_signatures. when an error occurs + // the stopped signal will be emitted here and the error will + // be emitted as a separate item after all active operations + // have completed. otherwise __nested_value.. is transparent. + // + + template + struct __nested_value_operation_base { + + _NestedValueReceiver __receiver_; + }; + + template + struct __receive_nested_value { + using __id = __receive_nested_value; + using __t = __receive_nested_value; + using receiver_concept = receiver_t; + + using _NestedValueReceiver = stdexec::__t<_NestedValueReceiverId>; + using __operation_base_interface_t = __operation_base_interface<_ErrorStorage>; + + __nested_value_operation_base<_NestedValueReceiver>* __nested_value_op_; + __operation_base_interface_t* __op_; + + template + void set_value(_Results&&... __results) noexcept { + auto __op = __op_; + stdexec::set_value( + static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_), + static_cast<_Results&&>(__results)...); + __op->nested_value_complete(); + } + + template + void set_error(_Error&& __error) noexcept { + auto __op = __op_; + stdexec::set_stopped(static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_)); + __op->store_error(static_cast<_Error&&>(__error)); + __op->nested_value_break(); + } + + void set_stopped() noexcept { + auto __op = __op_; + stdexec::set_stopped(static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_)); + __op->nested_value_complete(); + } + + using __env_t = decltype(__op_->env_from(__declval>())); + auto get_env() const noexcept -> __env_t { + return __op_->env_from(stdexec::get_env(__nested_value_op_->__receiver_)); + } + }; + + template + struct __nested_value_op { + using _NestedValueReceiver = stdexec::__t<_NestedValueReceiverId>; + using __base_t = __nested_value_operation_base<_NestedValueReceiver>; + using __operation_base_interface_t = __operation_base_interface<_ErrorStorage>; + + struct __t : __base_t { + using __id = __nested_value_op; + using __receiver = __receive_nested_value<_NestedValueReceiverId, _ErrorStorage>; + using __nested_value_op_t = stdexec::connect_result_t<_NestedValueSender, __receiver>; + + __nested_value_op_t __nested_value_op_; + __operation_base_interface_t* __op_; + + __t(_NestedValueReceiver __rcvr, _NestedValueSender __result, __operation_base_interface_t* __op) + noexcept( + __nothrow_move_constructible<_NestedValueReceiver> + && __nothrow_connectable<_NestedValueSender, __receiver>) + : __base_t{static_cast<_NestedValueReceiver&&>(__rcvr)} + , __nested_value_op_{stdexec::connect(static_cast<_NestedValueSender&&>(__result), __receiver{this, __op})} + , __op_{__op} {} + + void start() & noexcept { + __op_->nested_value_started(); + stdexec::start(__nested_value_op_); + } + }; + }; + + template + struct __nested_value_sender { + using __operation_base_interface_t = __operation_base_interface<_ErrorStorage>; + + struct __t { + using __id = __nested_value_sender; + using sender_concept = stdexec::sender_t; + + template + using __nested_value_op_t = stdexec::__t<__nested_value_op<_NestedValueSender, _NestedValueReceiverId, _ErrorStorage>>; + template + using __receiver = __receive_nested_value<_NestedValueReceiverId, _ErrorStorage>; + + _NestedValueSender __nested_value_; + __operation_base_interface_t* __op_; + + template _Self, class... _Env> + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept + -> stdexec::transform_completion_signatures< + stdexec::completion_signatures_of_t<_NestedValueSender, _Env...>, + stdexec::completion_signatures, + stdexec::__sigs::__default_set_value, + drop> { + return {}; + } + + template _Self, receiver _NestedValueReceiver> + static auto connect(_Self&& __self, _NestedValueReceiver&& __rcvr) + noexcept( + __nothrow_constructible_from< + __nested_value_op_t>, + _NestedValueReceiver, + _NestedValueSender, + __operation_base_interface_t*>) + -> __nested_value_op_t> { + return {static_cast<_NestedValueReceiver&&>(__rcvr), + static_cast<_NestedValueSender&&>(__self.__nested_value_), + __self.__op_}; + } + }; + }; + + // + // __next_.. is returned from set_next. Unlike the rest of the + // receivers here, the completion signals to the next receiver + // travel to the producer. + // Only set_value() and set_stopped() are allowed. + // - set_value() will signal the producer to emit the next + // sequence and cleanup the storage for the previous + // sequence. + // - set_stopped() will signal the producer to break out and + // send no more sequences. + // + + struct __next_operation_interface { + virtual ~__next_operation_interface() {} + virtual void nested_sequence_complete() noexcept = 0; + virtual void nested_sequence_break() noexcept = 0; + }; + + template + struct __next_operation_base : __next_operation_interface { + _NextReceiver __receiver_; + _OperationBase* __op_; + _NestedSeqOp __nested_seq_op_{}; + + __next_operation_base(_NextReceiver __receiver, _OperationBase* __op) + noexcept(__nothrow_move_constructible<_NextReceiver>) + : __next_operation_interface{} + , __receiver_ {static_cast<_NextReceiver&&>(__receiver)} + , __op_{__op} { + } + + void nested_sequence_complete() noexcept override { + stdexec::set_value(static_cast<_NextReceiver&&>(this->__receiver_)); + __op_->nested_sequence_complete(); + } + void nested_sequence_break() noexcept override { + stdexec::set_stopped(static_cast<_NextReceiver&&>(this->__receiver_)); + __op_->nested_sequence_break(); + } + }; + + // + // __receive_nested_values is subscribed to each nested sequence + // This forwards all the nested value senders to the output sequence. + // This wraps the nested value senders to capture and delay any + // error signals emitted by the nested value sender. + // This captures and delays any error signals received directly. + // set_stopped is ignored. This allows nested sequences to be + // stopped individually without stopping all the other nested + // sequences or the merge_each operation. + // + + template + struct __receive_nested_values { + using __id = __receive_nested_values; + using __t = __receive_nested_values; + using receiver_concept = receiver_t; + + __next_operation_interface* __next_seq_op_; + _OperationBase* __op_; + + using __error_storage_t = typename _OperationBase::__error_storage_t; + template + using __nested_value_sender_t = stdexec::__t<__nested_value_sender<_NestedValue, __error_storage_t>>; + + template + auto set_next(_NestedValue&& __nested_value) + noexcept( + __nothrow_callable< + exec::set_next_t, + decltype(__op_->__receiver_), + __nested_value_sender_t<_NestedValue>>) + -> next_sender auto { + return exec::set_next(__op_->__receiver_, + __nested_value_sender_t<_NestedValue>{ + static_cast<_NestedValue&&>(__nested_value), + __op_}); + } + + void set_value() noexcept { + __next_seq_op_->nested_sequence_complete(); + } + + template + void set_error(_Error&& __error) noexcept { + __op_->store_error(static_cast<_Error&&>(__error)); + __next_seq_op_->nested_sequence_break(); + } + + void set_stopped() noexcept { + __next_seq_op_->nested_sequence_complete(); + } + + using __env_t = typename _OperationBase::__nested_stop_env_t; + auto get_env() const noexcept -> __env_t { + return __op_->__nested_stop_.env_from(__op_->__receiver_); + } + }; + + struct _INVALID_ARGUMENT_TO_MERGE_WITH_REQUIRES_A_SEQUENCE_OF_SEQUENCES_ {}; + + template + struct __value_completions_error { + template + using __f = __mexception< + _INVALID_ARGUMENT_TO_MERGE_WITH_REQUIRES_A_SEQUENCE_OF_SEQUENCES_, + _WITH_SEQUENCE_<_Sequence>, + _WITH_SENDER_<_Sender>, + _WITH_ENVIRONMENT_<_Env>..., + _WITH_ARGUMENTS_<_Args...> + >; + }; + + struct __compute { + + // + // __nested_sequences extracts the types of all the nested sequences. + // + + template + struct __arg_of_t { + template + using __f = + stdexec::__meval && ...), + stdexec::__q, + __value_completions_error<_Sequence, _Sender, _Env...> + >::template __f, _Args...> + ; + }; + + template + struct __gather_sequences_t { + template + using __f = + stdexec::__gather_completion_signatures< + stdexec::completion_signatures_of_t<_Sender, _Env...>, + stdexec::set_value_t, + // if set_value + __arg_of_t<_Sequence, _Sender, _Env...>::template __f, + // else remove + stdexec::__mconst>::__f, + // concat to __types result + stdexec::__mtry_q< + stdexec::__mconcat>::template __f> + ::__f + >; + }; + + template + using __nested_sequences_from_item_type_t = + stdexec::__mapply< + stdexec::__if_c< + stdexec::__mvalid + && stdexec::__mvalid<__gather_sequences_t<_Sequence, _Sender, _Env...>::template __f>, + __gather_sequences_t<_Sequence, _Sender, _Env...>, + __value_completions_error<_Sequence, _Sender, _Env...>>, + stdexec::__completion_signatures_of_t<_Sender, _Env...>>; + + template + struct __nested_sequences_t { + + template + using __f = stdexec::__mapply< + stdexec::__munique>, + stdexec::__minvoke< + stdexec::__mconcat>, + __nested_sequences_from_item_type_t<_Sequence, _Senders, _Env...>...>>; + }; + + template + using __nested_sequences = __mapply<__nested_sequences_t<_Sequence, _Env...>, __item_types_of_t<_Sequence, _Env...>>; + + // + // __all_nested_values extracts the types of all the nested value senders. + // + + template + struct __all_nested_values_t { + + template + using __f = stdexec::__minvoke< + stdexec::__mconcat>, + __item_types_of_t<_Sequences, _Env...>...>; + }; + + template + using __all_nested_values = __mapply<__all_nested_values_t<_Env...>, __nested_sequences<_Sequence, _Env...>>; + + // + // __error_types extracts the types of all the errors emitted by all the senders in the list. + // + + template> + struct __error_types_t { + template + using __f = stdexec::error_types_of_t<_Sender, _Env, stdexec::__types>; + }; + + template + using __error_types = stdexec::__mapply< + stdexec::__mtransform< + __error_types_t<_Env...>, + stdexec::__mconcat>>, + _Senders>; + + // + // __errors extracts the types of all the errors emitted by: + // - all the senders of nested sequences + // - all the nested sequences + // - all the nested values senders + // This represents all the errors that are emitted on the + // output sequence. + // + + template + using __errors = stdexec::__minvoke< + stdexec::__mconcat>, + // always include std::exception_ptr + stdexec::__types, + // include errors from senders of the nested sequences + __error_types<__item_types_of_t<_Sequence, _Env...>, _Env...>, + // include errors from the nested sequences + __error_types<__merge_each::__compute::__nested_sequences<_Sequence, _Env...>, _Env...>, + // include errors from all the item type senders of all the nested sequences + __error_types<__merge_each::__compute::__all_nested_values<_Sequence, _Env...>, _Env...> + >; + + // + // __error_variant makes a variant type of all the errors + // that are emitted on the output sequence. This is used + // to store the first error and emit it as an item after + // all active operations are completed. + // + + template + using __error_variant = stdexec::__mapply< + __q, + __errors<_Sequence, _Env...>>; + + // + // __nested_values extracts the types of all the nested value senders and + // builds the item_types list for the merge_each sequence sender. + // + + template + using __nested_value_sender_t = stdexec::__t<__nested_value_sender<_NestedValueSender, _ErrorStorage>>; + + template + struct __nested_values_t { + + template + using __f = stdexec::__mapply< + stdexec::__munique>, + stdexec::__types< + __nested_value_sender_t<_AllItems, _ErrorStorage>..., + __t<__error_sender<_ErrorStorage>>>>; + }; + + template + using __nested_values = stdexec::__mapply< + __nested_values_t<__error_variant<_Sequence, _Env...>, _Env...>, + __all_nested_values<_Sequence, _Env...>>; + + // + // __nested_sequence_ops_variant makes a variant that contains the + // types of all the nested sequence operations. + // + + template + struct __nested_sequence_op_t { + template + using __f = subscribe_result_t<_Sequence, __receive_nested_values<_OperationBase>>; + }; + + template + using __operation_base_t = __operation_base<_Receiver, __error_variant<_Sequence, __env_with_inplace_stop_token_result_t>>>; + + template + using __nested_sequence_ops_variant = stdexec::__mapply< + stdexec::__mtransform< + __nested_sequence_op_t<__operation_base_t<_Sequence, _Receiver>>, + stdexec::__qq>, + __merge_each::__compute::__nested_sequences<_Sequence, __env_with_inplace_stop_token_result_t>> + >; + + }; + + // + // __receive_nested_sequence is connected to each sender of a nested sequence. + // The nested sequence is then subscribed and the operation is stored in a + // variant of all possible nested sequence operations. + // + + template + struct __receive_nested_sequence { + using __id = __receive_nested_sequence; + using __t = __receive_nested_sequence; + using receiver_concept = receiver_t; + + using _NextReceiver = stdexec::__t<_NextReceiverId>; + + using __next_op_base_t = __next_operation_base<_NextReceiver, _OperationBase, _NestedSeqOp>; + + __next_op_base_t* __next_seq_op_; + _OperationBase* __op_; + + template + auto set_value(_NestedSequence&& __sequence) noexcept { + using __nested_op_t = subscribe_result_t<_NestedSequence, __receive_nested_values<_OperationBase>>; + if constexpr ( + __nothrow_subscribable<_NestedSequence, __receive_nested_values<_OperationBase>> + && stdexec::__nothrow_constructible_from<_NestedSeqOp, __nested_op_t>) { + auto& __nested_seq_op = __next_seq_op_->__nested_seq_op_.emplace_from( + [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { + return subscribe(static_cast<_NestedSequence&&>(__sequence), static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); + }, + static_cast<_NestedSequence&&>(__sequence), + __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); + stdexec::start(__nested_seq_op); + } else { + STDEXEC_TRY { + auto& __nested_seq_op = __next_seq_op_->__nested_seq_op_.emplace_from( + [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { + return subscribe(static_cast<_NestedSequence&&>(__sequence), static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); + }, + static_cast<_NestedSequence&&>(__sequence), + __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); + stdexec::start(__nested_seq_op); + } + STDEXEC_CATCH_ALL { + __op_->store_error(std::current_exception()); + __op_->nested_sequence_break(); + } + } + } + + template + void set_error(_Error&& __error) noexcept { + __op_->store_error(static_cast<_Error&&>(__error)); + __op_->nested_sequence_break(); + } + + void set_stopped() noexcept { + __op_->nested_sequence_complete(); + } + + using __env_t = typename _OperationBase::__nested_stop_env_t; + auto get_env() const noexcept -> __env_t { + return __op_->__nested_stop_.env_from(__op_->__receiver_); + } + }; + + template + struct __next_sequence_op { + using _NextReceiver = stdexec::__t<_NextReceiverId>; + using __base_t = __next_operation_base<_NextReceiver, _OperationBase, _NestedSeqOp>; + struct __t : __base_t { + using __id = __next_sequence_op; + using __receiver = __receive_nested_sequence<_NextReceiverId, _OperationBase, _NestedSeqOp>; + using __nested_sequence_op_t = stdexec::connect_result_t<_NestedSequenceSender, __receiver>; + + _OperationBase* __op_; + __nested_sequence_op_t __nested_sequence_op_; + + __t(_NextReceiver __rcvr, _OperationBase* __op, _NestedSequenceSender __nested_sequence) + noexcept( + __nothrow_move_constructible<_NextReceiver> + && __nothrow_connectable<_NestedSequenceSender, __receiver>) + : __base_t{static_cast<_NextReceiver&&>(__rcvr), __op} + , __op_(__op) + , __nested_sequence_op_{stdexec::connect(static_cast<_NestedSequenceSender&&>(__nested_sequence), __receiver{this, __op_})} {} + + void start() & noexcept { + __op_->nested_sequence_started(); + stdexec::start(__nested_sequence_op_); + } + }; + }; + + template + struct __next_sequence_sender { + struct __t { + using __id = __next_sequence_sender; + + using sender_concept = stdexec::sender_t; + + template + using __next_sequence_op_t = stdexec::__t<__next_sequence_op<_NestedSequenceSender, _NextReceiverId, _OperationBase, _NestedSeqOp>>; + + _OperationBase* __op_; + _NestedSequenceSender __nested_sequence_; + + template _Self, class... _Env> + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept + -> stdexec::completion_signatures { + return {}; + } + + template _Self, receiver _NextReceiver> + static auto connect(_Self&& __self, _NextReceiver&& __rcvr) + noexcept( + __nothrow_constructible_from< + __next_sequence_op_t>, + _NextReceiver, + _OperationBase*, + _NestedSequenceSender>) + -> __next_sequence_op_t> { + return {static_cast<_NextReceiver&&>(__rcvr), + __self.__op_, + static_cast<_NestedSequenceSender&&>(__self.__nested_sequence_)}; + } + }; + }; + + // + // __receive_nested_sequences is subscribed to the input sequence of sequences + // each new sender of a nested sequence is placed in a __next_sequence_sender + // that is returned from set_next() + // + + template + struct __receive_nested_sequences { + using __id = __receive_nested_sequences; + using __t = __receive_nested_sequences; + using receiver_concept = receiver_t; + + template + using __next_sequence_sender_t = stdexec::__t<__next_sequence_sender<_NestedSequenceSender, _OperationBase, _NestedSeqOp>>; + + _OperationBase* __op_; + + template + auto set_next(_NestedSequenceSender&& __nested_sequence) + noexcept( + __nothrow_constructible_from< + __next_sequence_sender_t<_NestedSequenceSender>, + _OperationBase*, + _NestedSequenceSender>) + -> next_sender auto { + return __next_sequence_sender_t<_NestedSequenceSender>{__op_, static_cast<_NestedSequenceSender>(__nested_sequence)}; + } + + void set_value() noexcept { + __op_->sequence_complete(); + } + + template + void set_error(_Error&& __error) noexcept { + __op_->set_exception(static_cast<_Error&&>(__error)); + __op_->sequence_break(); + } + + void set_stopped() noexcept { + __op_->sequence_break(); + } + + using __env_t = typename _OperationBase::__nested_stop_env_t; + auto get_env() const noexcept -> __env_t { + return __op_->__nested_stop_.env_from(__op_->__receiver_); + } + }; + + + template + struct __operation { + using _Receiver = stdexec::__t<_ReceiverId>; + using __error_storage_t = __compute::__error_variant<_Sequence, __env_with_inplace_stop_token_result_t>>; + using __base_t = __operation_base<_Receiver, __error_storage_t>; + struct __t : __base_t { + using __id = __operation; + + using __nested_seq_op_t = __compute::__nested_sequence_ops_variant<_Sequence, _Receiver>; + + using __receiver = __receive_nested_sequences<__base_t, __nested_seq_op_t>; + + using __op_t = subscribe_result_t<_Sequence, __receiver>; + __op_t __op_; + + __t(_Receiver __rcvr, _Sequence __sequence) + noexcept( + __nothrow_subscribable<_Sequence, __receiver> + && __nothrow_move_constructible<_Receiver>) + : __base_t{static_cast<_Receiver&&>(__rcvr)} + ,__op_{subscribe(static_cast<_Sequence&&>(__sequence), __receiver{this})} {} + + void start() & noexcept { + this->__nested_stop_.register_token(this->__receiver_); + if (this->__nested_stop_.stop_requested()) { + // Stop has already been requested. Don't bother starting + // the child operations. + stdexec::set_stopped(static_cast<_Receiver&&>(this->__receiver_)); + } else { + this->sequence_started(); + stdexec::start(__op_); + } + } + }; + }; + + template + struct __subscribe_fn { + _Receiver& __rcvr_; + + template + auto operator()(__ignore, __ignore, _Sequence __sequence) + noexcept( + __nothrow_constructible_from< + __t<__operation<__id<_Receiver>, _Sequence>>, + _Receiver, + _Sequence>) + -> __t<__operation<__id<_Receiver>, _Sequence>> { + return { + static_cast<_Receiver&&>(__rcvr_), + static_cast<_Sequence&&>(__sequence)}; + } + }; + + struct _INVALID_ARGUMENTS_TO_MERGE_EACH_ { }; + + template + using __argument_error_t = __mexception< + _INVALID_ARGUMENTS_TO_MERGE_EACH_, + _WITH_SEQUENCE_<__child_of<_Self>>, + _WITH_ENVIRONMENT_<_Env>... + >; + + // + // merge_each is a sequence adaptor that takes a sequence of nested + // sequences and merges all the nested values from all the nested + // sequences into a single output sequence. + // + // the first error encountered will trigger a stop request for all + // active operations. The error is stored and is emitted only after + // all the active operations have completed. + // If the error was emitted from an item, a new item is emitted + // at the end to deliver the stored error. + // + // any nested sequence or nested value that completes with + // set_stopped will not cause any other operations to be stopped. + // This allows individual nested sequences to be stopped without + // breaking the merge of the remaining sequences. + // + + struct merge_each_t { + template + auto operator()(_Sequence&& __sequence) const + noexcept(__nothrow_decay_copyable<_Sequence>) + -> __well_formed_sequence_sender auto { + return make_sequence_expr( + __(), static_cast<_Sequence&&>(__sequence)); + } + + template _Self, class... _Env> + static auto get_item_types(_Self&&, _Env&&...) noexcept { + return __minvoke< + __mtry_catch<__q<__compute::__nested_values>, __q<__argument_error_t>>, + __child_of<_Self>, + __env_with_inplace_stop_token_result_t<_Env...>>(); + } + + template + struct __completions_t { + + template + using __f = __meval< + __concat_completion_signatures, + completion_signatures, + completion_signatures_of_t<__child_of<_Self>, _Env>, + completion_signatures_of_t<_Sequences, _Env>... + >; + }; + + template + using __completions = __mapply<__completions_t<_Self, _Env...>, __compute::__nested_sequences<__child_of<_Self>, _Env...>>; + + template _Self, class... _Env> + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept { + return __minvoke<__mtry_catch<__q<__completions>, __q<__argument_error_t>>, + _Self, + __env_with_inplace_stop_token_result_t<_Env...>>{}; + } + + static constexpr auto subscribe = + [](_Sequence&& __sndr, _Receiver __rcvr) noexcept( + __nothrow_callable<__sexpr_apply_t, _Sequence, __subscribe_fn<_Receiver>>) + -> __sexpr_apply_result_t<_Sequence, __subscribe_fn<_Receiver>> + { + static_assert(sender_expr_for<_Sequence, merge_each_t>); + return __sexpr_apply(static_cast<_Sequence&&>(__sndr), __subscribe_fn<_Receiver>{__rcvr}); + }; + + }; + } // namespace __merge_each + + using __merge_each::merge_each_t; + inline constexpr merge_each_t merge_each{}; +} // namespace exec diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index ae0775490..8a844fdb4 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -94,10 +94,10 @@ namespace exec { using __sequence_sndr::set_next_t; inline constexpr set_next_t set_next; - template + template using next_sender_of_t = decltype(exec::set_next( stdexec::__declval&>(), - stdexec::__declval<_Sequence>())); + stdexec::__declval<_Sender>())); namespace __sequence_sndr { @@ -661,6 +661,9 @@ namespace exec { using __sequence_sndr::subscribe_result_t; + template + concept __nothrow_subscribable = stdexec::__nothrow_callable; + template concept sequence_sender_to = sequence_receiver_from<_Receiver, _Sequence> && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { diff --git a/test/exec/CMakeLists.txt b/test/exec/CMakeLists.txt index eebb67cdd..ca8ff54f0 100644 --- a/test/exec/CMakeLists.txt +++ b/test/exec/CMakeLists.txt @@ -51,6 +51,8 @@ set(exec_test_sources sequence/test_iterate.cpp sequence/test_transform_each.cpp sequence/test_merge.cpp + sequence/test_merge_each.cpp + sequence/test_merge_each_threaded.cpp $<$:../execpools/test_tbb_thread_pool.cpp> $<$:../execpools/test_taskflow_thread_pool.cpp> $<$:../execpools/test_asio_thread_pool.cpp> diff --git a/test/exec/sequence/test_merge_each.cpp b/test/exec/sequence/test_merge_each.cpp new file mode 100644 index 000000000..34a77a534 --- /dev/null +++ b/test/exec/sequence/test_merge_each.cpp @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "exec/sequence/ignore_all_values.hpp" +#include "exec/sequence/merge_each.hpp" +#include "exec/sequence/merge.hpp" +#include "exec/sequence/empty_sequence.hpp" +#include "exec/sequence/iterate.hpp" +#include "exec/sequence_senders.hpp" +#include "exec/variant_sender.hpp" +#include "exec/static_thread_pool.hpp" +#include "exec/timed_thread_scheduler.hpp" +#include "stdexec/__detail/__meta.hpp" +#include "stdexec/__detail/__read_env.hpp" + +#include +#include +#include +#include +#include + +# include +# include +# include + +namespace { + using namespace std::chrono_literals; + using namespace exec; + namespace ex = stdexec; + + template + concept __equivalent = __sequence_sndr::__all_contained_in<_A, _B> + && __sequence_sndr::__all_contained_in<_B, _A> + && ex::__v> + == ex::__v>; + + struct null_receiver { + using __id = null_receiver; + using __t = null_receiver; + using receiver_concept = ex::receiver_t; + + template + void set_value(_Values&&...) noexcept { + } + + template + void set_error(_Error&& ) noexcept { + } + + void set_stopped() noexcept { + } + + [[nodiscard]] + auto get_env() const noexcept -> ex::env<> { + return {}; + } + + struct ignore_values_fn_t { + template + void operator()(_Vs&&...) const noexcept {} + }; + + template + [[nodiscard]] + auto set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) + -> next_sender auto { + return stdexec::upon_error(stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); + } + }; + + // a sequence adaptor that applies a function to each item + [[maybe_unused]] static constexpr auto then_each = [](auto f) { + return exec::transform_each(ex::then(f)); + }; + // a sequence adaptor that schedules each item to complete + // on the specified scheduler + [[maybe_unused]] static constexpr auto continues_each_on = [](auto sched) { + return exec::transform_each(ex::continues_on(sched)); + }; + // a sequence adaptor that schedules each item to complete + // on the specified scheduler after the specified duration + [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { + return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { + return sequence(schedule_after(sched, after), stdexec::just(vs...)); + })); + }; + // a sequence adaptor that applies a function to each item + // the function must produce a sequence + // all the sequences returned from the function are merged + [[maybe_unused]] static constexpr auto flat_map = [](auto&& f) { + auto map_merge = [](auto&& sequence, auto&& f) noexcept { + return merge_each(exec::transform_each( + static_cast(sequence), + ex::then(static_cast(f)))); + }; + return stdexec::__binder_back{{static_cast(f)}, {}, {}}; + }; + // when_all requires a successful completion + // however stop_after_on has no successful completion + // this uses variant_sender to add a successful completion + // (the successful completion will never occur) + [[maybe_unused]] static constexpr auto with_void = [](auto&& sender) noexcept + -> variant_sender< + stdexec::__call_result_t, + decltype(sender)> { + return {static_cast(sender)}; + }; + // with_stop_token_from adds get_stop_token query, that returns the + // token for the provided stop_source, to the receiver env + [[maybe_unused]] static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { + return ex::write_env(ex::prop{ex::get_stop_token, stop_source.get_token()}); + }; + // log_start completes with the provided sequence after printing provided string + [[maybe_unused]] auto log_start = [](auto sequence, auto message) { + return exec::sequence( + ex::read_env(ex::get_stop_token) + | stdexec::then([message](auto&& token) noexcept { + UNSCOPED_INFO(message + << (token.stop_requested() ? ", stop was requested" : ", stop not requested") + << ", on thread id: " << std::this_thread::get_id()); + }), + ex::just(sequence)); + }; + // log_sequence prints the message when each value in the sequence is emitted + [[maybe_unused]] auto log_sequence = [](auto sequence, auto message) { + return sequence + | then_each([message](auto&& value) mutable noexcept { + UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); + return value; + }); + }; + // emits_stopped completes with set_stopped after printing info + [[maybe_unused]] auto emits_stopped = []() { + return ex::just() + | stdexec::let_value([]() noexcept { + UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); + }; + // emits_error completes with set_error(error) after printing info + [[maybe_unused]] auto emits_error = [](auto error) { + return ex::just() + | stdexec::let_value([error]() noexcept { + UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); + }; + +#if STDEXEC_HAS_STD_RANGES() + + // a sequence of numbers from itoa() + [[maybe_unused]] static constexpr auto range = [](auto from, auto to) { + return exec::iterate(std::views::iota(from, to)); + }; + + template + struct as_sequence_t : Sender { + using sender_concept = sequence_sender_t; + using item_types = exec::item_types; + auto subscribe(auto receiver) { + return connect(set_next(receiver, *static_cast(this)), receiver); + } + }; + + TEST_CASE( + "merge_each - merge two sequence senders of no elements", + "[sequence_senders][merge_each][empty_sequence]") { + using empty_sequence_t = stdexec::__call_result_t; + + [[maybe_unused]] std::array array{ + empty_sequence(), + empty_sequence()}; + + [[maybe_unused]] auto sequences = iterate(std::views::all(array)); + using sequences_t = decltype(sequences); + + STATIC_REQUIRE(ex::__ok< + item_types_of_t>); + STATIC_REQUIRE(ex::__ok< + stdexec::completion_signatures_of_t>); + + [[maybe_unused]] auto merged = merge_each(sequences); + using merged_t = decltype(merged); + + + STATIC_REQUIRE(ex::__ok< + item_types_of_t>); + STATIC_REQUIRE(ex::__ok< + stdexec::completion_signatures_of_t>); + + STATIC_REQUIRE(ex::__callable); + + int count = 0; + + auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ + ++count; + UNSCOPED_INFO("item: " << x + << ", on thread id: " << std::this_thread::get_id()); + }))); + + CHECK(count == 0); + CHECK(v.has_value() == true); + } + + TEST_CASE( + "merge_each - merge two sequence senders of integers", + "[sequence_senders][merge_each][empty_sequence]") { + + using range_sender_t = stdexec::__call_result_t; + + [[maybe_unused]] std::array array{ + range(100,120), + range(200,220)}; + + [[maybe_unused]] auto sequences = iterate(std::views::all(array)); + using sequences_t = decltype(sequences); + + STATIC_REQUIRE(ex::__ok< + item_types_of_t>); + STATIC_REQUIRE(ex::__ok< + stdexec::completion_signatures_of_t>); + + [[maybe_unused]] auto merged = merge_each(sequences); + using merged_t = decltype(merged); + + STATIC_REQUIRE(ex::__ok< + item_types_of_t>); + STATIC_REQUIRE(ex::__ok< + stdexec::completion_signatures_of_t>); + + STATIC_REQUIRE(ex::__callable); + + int count = 0; + + auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ + ++count; + UNSCOPED_INFO("item: " << x + << ", on thread id: " << std::this_thread::get_id()); + }))); + + CHECK(count == 40); + CHECK(v.has_value() == true); + } + + TEST_CASE( + "merge_each - merge sequence of two sequence senders of integers and one empty sequence", + "[sequence_senders][merge_each][merge][empty_sequence]") { + + using range_sequence_t = stdexec::__call_result_t; + STATIC_REQUIRE(__well_formed_sequence_sender); + STATIC_REQUIRE_FALSE(std::same_as< + item_types_of_t, + item_types<>>); + STATIC_REQUIRE(__equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_error_t(std::exception_ptr), + ex::set_stopped_t(), + ex::set_value_t()>>); + + using just_range_sender_t = ex::__call_result_t; + STATIC_REQUIRE(__equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_value_t(range_sequence_t)>>); + + using empty_sequence_t = stdexec::__call_result_t; + STATIC_REQUIRE(__well_formed_sequence_sender); + STATIC_REQUIRE(std::same_as< + item_types_of_t, + item_types<>>); + STATIC_REQUIRE(__equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_value_t()>>); + + using just_empty_sender_t = ex::__call_result_t; + STATIC_REQUIRE(__equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_value_t(empty_sequence_t)>>); + + auto sequences = merge( + ex::just(range(100, 120)), + ex::just(empty_sequence()), + ex::just(range(200, 220))); + using sequences_t = decltype(sequences); + + STATIC_REQUIRE(ex::__ok< + __item_types_of_t>); + STATIC_REQUIRE(ex::__ok< + ex::completion_signatures_of_t>); + + STATIC_REQUIRE(__equivalent< + __item_types_of_t, + item_types>); + STATIC_REQUIRE(__equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_stopped_t(), + ex::set_value_t()>>); + + auto merged = merge_each(sequences); + using merged_t = decltype(merged); + + STATIC_REQUIRE(ex::__ok< + __item_types_of_t>); + STATIC_REQUIRE(__equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_error_t(std::exception_ptr), + ex::set_stopped_t(), + ex::set_value_t()>>); + + int count = 0; + + auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ + ++count; + UNSCOPED_INFO("item: " << x + << ", on thread id: " << std::this_thread::get_id()); + }))); + + CHECK(count == 40); + CHECK(v.has_value() == true); + } + +// TODO - fix problem with stopping +#if 0 + TEST_CASE( + "merge_each - merge_each sender stops when a nested sequence fails", + "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { + + auto sequences = merge( + log_start(range(100, 120), "range 100-120"), + ex::just(emits_error(std::runtime_error{"failed sequence "})), + log_start(range(200, 220), "range 200-220") + ); + + [[maybe_unused]] auto merged = merge_each(std::move(sequences)); + + int count = 0; + + auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ + ++count; + UNSCOPED_INFO("item: " << x + << ", on thread id: " << std::this_thread::get_id()); + }))); + + CHECK(count == 20); + CHECK(v.has_value() == false); + } + #endif // 0 + +#endif // STDEXEC_HAS_STD_RANGES() + +} diff --git a/test/exec/sequence/test_merge_each_threaded.cpp b/test/exec/sequence/test_merge_each_threaded.cpp new file mode 100644 index 000000000..61cab3470 --- /dev/null +++ b/test/exec/sequence/test_merge_each_threaded.cpp @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "exec/sequence/ignore_all_values.hpp" +#include "exec/sequence/merge_each.hpp" +#include "exec/sequence/merge.hpp" +#include "exec/sequence/empty_sequence.hpp" +#include "exec/sequence/iterate.hpp" +#include "exec/sequence_senders.hpp" +#include "exec/variant_sender.hpp" +#include "exec/static_thread_pool.hpp" +#include "exec/timed_thread_scheduler.hpp" +#include "stdexec/__detail/__meta.hpp" +#include "stdexec/__detail/__read_env.hpp" + +#include +#include +#include +#include +#include + +# include +# include +# include + +namespace { + using namespace std::chrono_literals; + using namespace exec; + namespace ex = stdexec; + + template + concept __equivalent = __sequence_sndr::__all_contained_in<_A, _B> + && __sequence_sndr::__all_contained_in<_B, _A> + && ex::__v> + == ex::__v>; + + struct null_receiver { + using __id = null_receiver; + using __t = null_receiver; + using receiver_concept = ex::receiver_t; + + template + void set_value(_Values&&...) noexcept { + } + + template + void set_error(_Error&& ) noexcept { + } + + void set_stopped() noexcept { + } + + [[nodiscard]] + auto get_env() const noexcept -> ex::env<> { + return {}; + } + + struct ignore_values_fn_t { + template + void operator()(_Vs&&...) const noexcept {} + }; + + template + [[nodiscard]] + auto set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) + -> next_sender auto { + return stdexec::upon_error(stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); + } + }; + + // a sequence adaptor that applies a function to each item + [[maybe_unused]] static constexpr auto then_each = [](auto f) { + return exec::transform_each(ex::then(f)); + }; + // a sequence adaptor that schedules each item to complete + // on the specified scheduler + [[maybe_unused]] static constexpr auto continues_each_on = [](auto sched) { + return exec::transform_each(ex::continues_on(sched)); + }; + // a sequence adaptor that schedules each item to complete + // on the specified scheduler after the specified duration + [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { + return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { + return sequence(schedule_after(sched, after), stdexec::just(vs...)); + })); + }; + // a sequence adaptor that applies a function to each item + // the function must produce a sequence + // all the sequences returned from the function are merged + [[maybe_unused]] static constexpr auto flat_map = [](auto&& f) { + auto map_merge = [](auto&& sequence, auto&& f) noexcept { + return merge_each(exec::transform_each( + static_cast(sequence), + ex::then(static_cast(f)))); + }; + return stdexec::__binder_back{{static_cast(f)}, {}, {}}; + }; + // when_all requires a successful completion + // however stop_after_on has no successful completion + // this uses variant_sender to add a successful completion + // (the successful completion will never occur) + [[maybe_unused]] static constexpr auto with_void = [](auto&& sender) noexcept + -> variant_sender< + stdexec::__call_result_t, + decltype(sender)> { + return {static_cast(sender)}; + }; + // with_stop_token_from adds get_stop_token query, that returns the + // token for the provided stop_source, to the receiver env + [[maybe_unused]] static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { + return ex::write_env(ex::prop{ex::get_stop_token, stop_source.get_token()}); + }; + // log_start completes with the provided sequence after printing provided string + [[maybe_unused]] auto log_start = [](auto sequence, auto message) { + return exec::sequence( + ex::read_env(ex::get_stop_token) + | stdexec::then([message](auto&& token) noexcept { + UNSCOPED_INFO(message + << (token.stop_requested() ? ", stop was requested" : ", stop not requested") + << ", on thread id: " << std::this_thread::get_id()); + }), + ex::just(sequence)); + }; + // log_sequence prints the message when each value in the sequence is emitted + [[maybe_unused]] auto log_sequence = [](auto sequence, auto message) { + return sequence + | then_each([message](auto&& value) mutable noexcept { + UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); + return value; + }); + }; + // emits_stopped completes with set_stopped after printing info + [[maybe_unused]] auto emits_stopped = []() { + return ex::just() + | stdexec::let_value([]() noexcept { + UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); + }; + // emits_error completes with set_error(error) after printing info + [[maybe_unused]] auto emits_error = [](auto error) { + return ex::just() + | stdexec::let_value([error]() noexcept { + UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); + }; + +#if STDEXEC_HAS_STD_RANGES() + + // a sequence of numbers from itoa() + [[maybe_unused]] static constexpr auto range = [](auto from, auto to) { + return exec::iterate(std::views::iota(from, to)); + }; + + template + struct as_sequence_t : Sender { + using sender_concept = sequence_sender_t; + using item_types = exec::item_types; + auto subscribe(auto receiver) { + return connect(set_next(receiver, *static_cast(this)), receiver); + } + }; + + TEST_CASE( + "merge_each - merge_each sender merges all items from multiple threads", + "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { + + exec::static_thread_pool ctx0{1}; + ex::scheduler auto sched0 = ctx0.get_scheduler(); + exec::static_thread_pool ctx1{1}; + ex::scheduler auto sched1 = ctx1.get_scheduler(); + exec::static_thread_pool ctx2{1}; + ex::scheduler auto sched2 = ctx2.get_scheduler(); + + auto sequences = merge( + ex::just(range(100, 120) | continues_each_on(sched1)), + ex::just(empty_sequence()), + ex::just(range(200, 220) | continues_each_on(sched2))); + + auto merged = merge_each(sequences); + + int count = 0; + + auto v = ex::sync_wait(ignore_all_values(merged | continues_each_on(sched0) | then_each([&count](int x){ + ++count; + UNSCOPED_INFO("item: " << x + << ", on thread id: " << std::this_thread::get_id()); + }))); + + CHECK(count == 40); + CHECK(v.has_value() == true); + } + + TEST_CASE( + "merge_each - merge_each sender stops on failed item while merging all items from multiple threads", + "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { + + exec::static_thread_pool ctx0{1}; + ex::scheduler auto sched0 = ctx0.get_scheduler(); + exec::timed_thread_context ctx1; + ex::scheduler auto sched1 = ctx1.get_scheduler(); + auto origin = now(sched1); + + auto elapsed_ms = [&sched1, origin](){ + using namespace std::chrono; + return duration_cast(now(sched1) - origin).count(); + }; + + auto stop_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after) { + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms]() noexcept { + UNSCOPED_INFO("requesting stop - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); + }; + + auto error_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms, error]() noexcept { + UNSCOPED_INFO(error.what() << " - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); + }; + + // a sequence whose items are sequences + auto sequences = merge( + ex::just(stop_after_on(sched1, 10ms)), // no items + ex::just(range(100, 120)), // int items + ex::just(empty_sequence()), // no items + ex::just(range(200, 220)), // int items + ex::just(error_after_on(sched1, 40ms, + std::runtime_error{"failed sequence "})) // no items + ); + + // apply delays_each_on to every sequence item and + // merge all the new sequences + auto merged = sequences + | flat_map([sched1](auto sequence){ + return sequence | delays_each_on(sched1, 10ms); + }); + + int count = 0; + + auto v = ex::sync_wait( + ex::when_all( + with_void(stop_after_on(sched1, 50ms)), + ignore_all_values(merged + | continues_each_on(sched0) // serializes output on the sched0 strand + | then_each([&count, elapsed_ms](int x){ + ++count; + UNSCOPED_INFO("item: " << x + << ", arrived at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return count; + })) + )); + + CHECK(v.has_value() == false); + CHECK(count < 40); + CHECK(count > 4); + } + + TEST_CASE( + "merge_each - merge_each sender stops while merging all items from multiple threads", + "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { + + exec::static_thread_pool ctx0{1}; + ex::scheduler auto sched0 = ctx0.get_scheduler(); + exec::timed_thread_context ctx1; + ex::scheduler auto sched1 = ctx1.get_scheduler(); + auto origin = now(sched1); + + auto elapsed_ms = [&sched1, origin](){ + using namespace std::chrono; + return duration_cast(now(sched1) - origin).count(); + }; + + auto stop_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after) { + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms]() noexcept { + UNSCOPED_INFO("requesting stop - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); + }; + + auto error_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms, error]() noexcept { + UNSCOPED_INFO(error.what() << " - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); + }; + + // a sequence whose items are sequences + auto sequences = merge( + ex::just(stop_after_on(sched1, 10ms)), // no items + ex::just(range(100, 120)), // int items + ex::just(empty_sequence()), // no items + ex::just(range(200, 220)), // int items + ex::just(error_after_on(sched1, 50ms, + std::runtime_error{"failed sequence "})) // no items + ); + + // apply delays_each_on to every sequence item and + // merge all the new sequences + auto merged = sequences + | flat_map([sched1](auto sequence){ + return sequence | delays_each_on(sched1, 10ms); + }); + + int count = 0; + + auto v = ex::sync_wait( + ex::when_all( + with_void(stop_after_on(sched1, 40ms)), + ignore_all_values(merged + | continues_each_on(sched0) // serializes output on the sched0 strand + | then_each([&count, elapsed_ms](int x){ + ++count; + UNSCOPED_INFO("item: " << x + << ", arrived at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return count; + })) + )); + + CHECK(v.has_value() == false); + CHECK(count < 40); + CHECK(count > 4); + } + +#endif // STDEXEC_HAS_STD_RANGES() + +} From 5117f8b95df1a9accfc1407cae2058070c0029ba Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 18 Oct 2025 19:36:12 +0000 Subject: [PATCH 12/39] provide aliases for exec::materialize_t and exec::dematerialize_t --- include/exec/materialize.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/exec/materialize.hpp b/include/exec/materialize.hpp index d59dd1b45..471066973 100644 --- a/include/exec/materialize.hpp +++ b/include/exec/materialize.hpp @@ -127,7 +127,8 @@ namespace exec { }; } // namespace __materialize - inline constexpr __materialize::__materialize_t materialize; + using materialize_t = __materialize::__materialize_t; + inline constexpr materialize_t materialize; namespace __dematerialize { using namespace stdexec; @@ -235,5 +236,6 @@ namespace exec { }; } // namespace __dematerialize - inline constexpr __dematerialize::__dematerialize_t dematerialize; + using dematerialize_t = __dematerialize::__dematerialize_t; + inline constexpr dematerialize_t dematerialize; } // namespace exec From d63efcfcf61b2339443d4e1440116cb123721656 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 18 Oct 2025 19:37:33 +0000 Subject: [PATCH 13/39] fix compiler error construction in merge --- include/exec/sequence/merge.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/exec/sequence/merge.hpp b/include/exec/sequence/merge.hpp index 01511e6ef..7c0d6bb0b 100644 --- a/include/exec/sequence/merge.hpp +++ b/include/exec/sequence/merge.hpp @@ -157,11 +157,11 @@ namespace exec { struct _INVALID_ARGUMENTS_TO_MERGE_ { }; - template + template using __error_t = __mexception< _INVALID_ARGUMENTS_TO_MERGE_, __children_of<_Self, __q<_WITH_SEQUENCES_>>, - _WITH_ENVIRONMENT_<_Env> + _WITH_ENVIRONMENT_<_Env>... >; template From 41a2f093f11e74968f48238f828858b158b7ce35 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 18 Oct 2025 19:39:27 +0000 Subject: [PATCH 14/39] fix some ordering and error handling issues in merge_each --- include/exec/sequence/merge_each.hpp | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/include/exec/sequence/merge_each.hpp b/include/exec/sequence/merge_each.hpp index be54b880a..1fb94fa7a 100644 --- a/include/exec/sequence/merge_each.hpp +++ b/include/exec/sequence/merge_each.hpp @@ -154,7 +154,7 @@ namespace exec { ~__operation_base_interface(){} virtual void nested_value_started() noexcept = 0; virtual void nested_value_complete() noexcept = 0; - virtual bool nested_value_fail() noexcept = 0; + virtual bool nested_sequence_fail() noexcept = 0; virtual void nested_value_break() noexcept = 0; virtual void error_complete() noexcept = 0; @@ -175,7 +175,7 @@ namespace exec { void store_error(_Error&& __error) noexcept( __nothrow_callable), _ErrorStorage&, _Error>) { - if (this->nested_value_fail()) { + if (this->nested_sequence_fail()) { // We are the first child to complete with an error, so we must save the error. (Any // subsequent errors are ignored.) if constexpr (noexcept(__error_storage_->template emplace<_Error>(static_cast<_Error&&>(__error)))) { @@ -386,7 +386,7 @@ namespace exec { void nested_value_complete() noexcept override { complete_if_none_active(); } - bool nested_value_fail() noexcept override { + bool nested_sequence_fail() noexcept override { switch (__completion_.exchange(__completion_t::__error)) { case __completion_t::__started: // We must request stop. When the previous state is __error or __stopped, then stop has @@ -491,23 +491,25 @@ namespace exec { void set_value(_Results&&... __results) noexcept { auto __op = __op_; stdexec::set_value( - static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_), - static_cast<_Results&&>(__results)...); + static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_) + , static_cast<_Results&&>(__results)...); __op->nested_value_complete(); } template void set_error(_Error&& __error) noexcept { auto __op = __op_; - stdexec::set_stopped(static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_)); - __op->store_error(static_cast<_Error&&>(__error)); + stdexec::set_error( + static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_) + , static_cast<_Error&&>(__error)); __op->nested_value_break(); } void set_stopped() noexcept { auto __op = __op_; - stdexec::set_stopped(static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_)); - __op->nested_value_complete(); + stdexec::set_stopped( + static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_)); + __op->nested_value_break(); } using __env_t = decltype(__op_->env_from(__declval>())); @@ -565,9 +567,7 @@ namespace exec { static auto get_completion_signatures(_Self&&, _Env&&...) noexcept -> stdexec::transform_completion_signatures< stdexec::completion_signatures_of_t<_NestedValueSender, _Env...>, - stdexec::completion_signatures, - stdexec::__sigs::__default_set_value, - drop> { + stdexec::completion_signatures> { return {}; } @@ -619,12 +619,14 @@ namespace exec { } void nested_sequence_complete() noexcept override { + auto& __op = *__op_; stdexec::set_value(static_cast<_NextReceiver&&>(this->__receiver_)); - __op_->nested_sequence_complete(); + __op.nested_sequence_complete(); } void nested_sequence_break() noexcept override { + auto& __op = *__op_; stdexec::set_stopped(static_cast<_NextReceiver&&>(this->__receiver_)); - __op_->nested_sequence_break(); + __op.nested_sequence_break(); } }; @@ -810,9 +812,7 @@ namespace exec { // include errors from senders of the nested sequences __error_types<__item_types_of_t<_Sequence, _Env...>, _Env...>, // include errors from the nested sequences - __error_types<__merge_each::__compute::__nested_sequences<_Sequence, _Env...>, _Env...>, - // include errors from all the item type senders of all the nested sequences - __error_types<__merge_each::__compute::__all_nested_values<_Sequence, _Env...>, _Env...> + __error_types<__merge_each::__compute::__nested_sequences<_Sequence, _Env...>, _Env...> >; // From 28d9367add4d1105f7a620da0dbf91df67a2935c Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 18 Oct 2025 20:40:31 +0000 Subject: [PATCH 15/39] improve compile errors for sequence senders --- include/exec/sequence_senders.hpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 8a844fdb4..e2563a4ec 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -22,11 +22,18 @@ #include "stdexec/__detail/__diagnostics.hpp" namespace exec { + namespace __errs { + template + struct _WITH_SEQUENCE_; + + template + struct _WITH_SEQUENCES_; + } template - struct _WITH_SEQUENCE_; + using _WITH_SEQUENCE_ = __errs::_WITH_SEQUENCE_>; template - struct _WITH_SEQUENCES_; + using _WITH_SEQUENCES_ = __errs::_WITH_SEQUENCES_...>; struct sequence_sender_t : stdexec::sender_t { }; @@ -241,7 +248,7 @@ namespace exec { using __tag_invoke::tag_invoke; // This ought to cause a hard error that indicates where the problem is. using __item_types_t - [[maybe_unused]] = tag_invoke_result_t; + [[maybe_unused]] = tag_invoke_result_t; return static_cast<__debug::__item_types (*)()>(nullptr); } else { using __result_t = __unrecognized_sequence_error_t<_Sequence, _Env>; @@ -435,6 +442,10 @@ namespace exec { stdexec::__mconst>::__f >; + // __sequence_completion_signatures_of_t + // makes a sender look like a sequence with itself as the only item + // + template using __sequence_completion_signatures_of_t = stdexec::__mapply< stdexec::__mtransform< @@ -511,7 +522,7 @@ namespace exec { // item_types_of_t to check that the actual completions and item_types // match the expected completions and values. using __checked_signatures - [[maybe_unused]] = completion_signatures_of_t<_Sequence, env_of_t<_Receiver>>; + [[maybe_unused]] = __sequence_completion_signatures_of_t<_Sequence, env_of_t<_Receiver>>; using __checked_item_types [[maybe_unused]] = item_types_of_t<_Sequence, env_of_t<_Receiver>>; } else { @@ -777,7 +788,7 @@ namespace exec { } else { using __items_t = __item_types_of_t<_Sequence, _Env...>; if constexpr (stdexec::__same_as<__items_t, __sequence_sndr::__unrecognized_sequence_error_t<_Sequence, _Env...>>) { - static_assert(stdexec::__mnever<__items_t>, STDEXEC_ERROR_CANNOT_COMPUTE_COMPLETION_SIGNATURES); + static_assert(stdexec::__mnever<__items_t>, STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR); } else if constexpr (stdexec::__merror<__items_t>) { static_assert( !stdexec::__merror<__items_t>, STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR); @@ -833,7 +844,7 @@ namespace exec { void __debug_sequence_sender(_Sequence&& __sequence, const _Env&) { if constexpr (!__is_debug_env<_Env>) { if constexpr (sequence_sender_in<_Sequence, _Env>) { - using __sigs_t = stdexec::__completion_signatures_of_t<_Sequence, __debug_env_t<_Env>>; + using __sigs_t = __sequence_completion_signatures_of_t<_Sequence, __debug_env_t<_Env>>; using __item_types_t = __sequence_sndr::__item_types_of_t<_Sequence, __debug_env_t<_Env>>; using __receiver_t = __debug_sequence_sender_receiver, _Env, __sigs_t, __item_types_t>; if constexpr (!std::same_as<__sigs_t, __debug::__completion_signatures> || !std::same_as<__item_types_t, __debug::__item_types>) { From be05244ede164cbf6a888041791f589075a3e675 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 18 Oct 2025 21:39:48 +0000 Subject: [PATCH 16/39] prevent trailing return types from causing subscribe, get_completion_signatures, and get_item_types to SFINAE away. This helps compiler errors to have better information --- include/exec/__detail/__basic_sequence.hpp | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/include/exec/__detail/__basic_sequence.hpp b/include/exec/__detail/__basic_sequence.hpp index 0b2bdf857..102958364 100644 --- a/include/exec/__detail/__basic_sequence.hpp +++ b/include/exec/__detail/__basic_sequence.hpp @@ -21,6 +21,10 @@ #include "../../stdexec/__detail/__basic_sender.hpp" #include "../sequence_senders.hpp" +#include "stdexec/__detail/__completion_signatures.hpp" +#include "stdexec/__detail/__concepts.hpp" +#include "stdexec/__detail/__debug.hpp" +#include namespace exec { ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -63,7 +67,29 @@ namespace exec { return _Self::__tag().get_env(*this); } + // make sure that get_completion_signatures does not SFINAE out + // when the trailing return-type is invalid but keep the + // trailing return-type when it is valid + struct get_completion_signatures_sfinae { + template _Self, class... _Env> + auto operator()(_Self&& __self, _Env&&... __env) const + -> decltype(__self.__tag().get_completion_signatures( + static_cast<_Self&&>(__self), + static_cast<_Env&&>(__env)...)) { + return {}; + } + }; + template _Self, class... _Env> + requires + (stdexec::__is_debug_env<_Env> || ... || false) + || (!stdexec::__callable) + static auto get_completion_signatures(_Self&& __self, _Env&&... __env) { + return __self.__tag().get_completion_signatures( + static_cast<_Self&&>(__self), + static_cast<_Env&&>(__env)...); + } template _Self, class... _Env> + requires (!stdexec::__is_debug_env<_Env> && ... && true) static auto get_completion_signatures(_Self&& __self, _Env&&... __env) -> decltype(__self.__tag().get_completion_signatures( static_cast<_Self&&>(__self), @@ -71,7 +97,29 @@ namespace exec { return {}; } + // make sure that get_item_types does not SFINAE out + // when the trailing return-type is invalid but keep the + // trailing return-type when it is valid + struct get_item_types_sfinae { + template _Self, class... _Env> + auto operator()(_Self&& __self, _Env&&... __env) const + -> decltype(__self.__tag().get_item_types( + static_cast<_Self&&>(__self), + static_cast<_Env&&>(__env)...)) { + return {}; + } + }; template _Self, class... _Env> + requires + (stdexec::__is_debug_env<_Env> || ... || false) + || (!stdexec::__callable) + static auto get_item_types(_Self&& __self, _Env&&... __env) { + return __self.__tag().get_item_types( + static_cast<_Self&&>(__self), + static_cast<_Env&&>(__env)...); + } + template _Self, class... _Env> + requires (!stdexec::__is_debug_env<_Env> && ... && true) static auto get_item_types(_Self&& __self, _Env&&... __env) -> decltype(__self.__tag().get_item_types( static_cast<_Self&&>(__self), @@ -79,7 +127,28 @@ namespace exec { return {}; } + // make sure that subscribe does not SFINAE out + // when the trailing return-type is invalid but keep the + // trailing return-type when it is valid + struct subscribe_sfinae { + template _Self, stdexec::receiver _Receiver> + auto operator()(_Self&& __self, _Receiver&& __rcvr) const noexcept(noexcept( + __self.__tag().subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)))) + -> decltype(__self.__tag() + .subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr))) { + return __tag_t::subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)); + } + }; + template _Self, stdexec::receiver _Receiver> + requires + stdexec::__is_debug_env> + || (!stdexec::__callable) + static auto subscribe(_Self&& __self, _Receiver&& __rcvr) noexcept(noexcept( + __self.__tag().subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)))) { + return __tag_t::subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)); + } template _Self, stdexec::receiver _Receiver> + requires (!stdexec::__is_debug_env>) static auto subscribe(_Self&& __self, _Receiver&& __rcvr) noexcept(noexcept( __self.__tag().subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)))) -> decltype(__self.__tag() From b1b161490176deac05113e6e234cc4cb031b433a Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 18 Oct 2025 21:40:52 +0000 Subject: [PATCH 17/39] add the option to specify a scheduler to iterate. This is needed in tests that use the test_scheduler --- include/exec/sequence/iterate.hpp | 107 +++++++++++++++++++++------- test/exec/sequence/test_iterate.cpp | 3 +- 2 files changed, 82 insertions(+), 28 deletions(-) diff --git a/include/exec/sequence/iterate.hpp b/include/exec/sequence/iterate.hpp index 1a71dd255..78c6cd268 100644 --- a/include/exec/sequence/iterate.hpp +++ b/include/exec/sequence/iterate.hpp @@ -17,6 +17,10 @@ #pragma once #include "../../stdexec/__detail/__config.hpp" +#include "stdexec/__detail/__concepts.hpp" +#include "stdexec/__detail/__env.hpp" +#include "stdexec/__detail/__sender_introspection.hpp" +#include "stdexec/__detail/__tuple.hpp" #if STDEXEC_HAS_STD_RANGES() @@ -35,6 +39,11 @@ namespace exec { namespace __iterate { using namespace stdexec; + template + using __range_of_t = stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Data)>; + template + using __scheduler_of_t = stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Data)>; + template struct __operation_base { STDEXEC_ATTRIBUTE(no_unique_address) _Iterator __iterator_; @@ -79,18 +88,18 @@ namespace exec { using __sender_t = stdexec::__t<__sender, std::ranges::sentinel_t<_Range>>>; - template + template struct __operation { struct __t; }; - template + template struct __next_receiver { struct __t { using _Receiver = stdexec::__t<_ReceiverId>; using __id = __next_receiver; using receiver_concept = stdexec::receiver_t; - stdexec::__t<__operation<_Range, _ReceiverId>>* __op_; + stdexec::__t<__operation<_Scheduler, _Range, _ReceiverId>>* __op_; void set_value() noexcept { __op_->__start_next(); @@ -106,23 +115,32 @@ namespace exec { }; }; - template - struct __operation<_Range, _ReceiverId>::__t : __operation_base_t<_Range> { + struct trampoline_t { + operator trampoline_scheduler () const noexcept { return {}; } + }; + + template + struct __operation<_Scheduler, _Range, _ReceiverId>::__t : __operation_base_t<_Range> { using _Receiver = stdexec::__t<_ReceiverId>; + using __scheduler_t = _Scheduler; + + using __next_receiver_t = stdexec::__t<__next_receiver<__scheduler_t, _Range, _ReceiverId>>; + + __scheduler_t __scheduler_; + _Receiver __rcvr_; using __item_sender_t = - __result_of, __sender_t<_Range>>; - using __next_receiver_t = stdexec::__t<__next_receiver<_Range, _ReceiverId>>; + __result_of, __sender_t<_Range>>; std::optional< connect_result_t, __next_receiver_t> > __op_{}; - trampoline_scheduler __scheduler_{}; void __start_next() noexcept { - if (this->__iterator_ == this->__sentinel_) { + if (stdexec::get_stop_token(this->__rcvr_).stop_requested() + || this->__iterator_ == this->__sentinel_) { stdexec::set_value(static_cast<_Receiver&&>(__rcvr_)); } else { @@ -151,14 +169,25 @@ namespace exec { using _ReceiverId = __id<_Receiver>; _Receiver __rcvr_; - template - using __operation_t = __t<__operation<__decay_t<_Range>, _ReceiverId>>; - - template - auto operator()(__ignore, _Range&& __range) noexcept(__nothrow_move_constructible<_Receiver>) - -> __operation_t<_Range> { + template + using __scheduler_t = stdexec::__if_c< + stdexec::__decays_to> + , trampoline_scheduler + , __scheduler_of_t<_Data>>; + + template + using __operation_t = __t<__operation< + __scheduler_t<_Data> + , __decay_t<__range_of_t<_Data>> + , _ReceiverId>>; + + template + auto operator()(__ignore, _Data&& __data) noexcept(__nothrow_move_constructible<_Receiver>) + -> __operation_t<_Data> { + auto [__scheduler, __range] = static_cast<_Data&&>(__data); return { {std::ranges::begin(__range), std::ranges::end(__range)}, + __scheduler, static_cast<_Receiver&&>(__rcvr_) }; } @@ -167,22 +196,45 @@ namespace exec { struct iterate_t { template requires __decay_copyable<_Range> - auto operator()(_Range&& __range) const -> __well_formed_sequence_sender auto { - return make_sequence_expr(__decay_t<_Range>{static_cast<_Range&&>(__range)}); + auto operator()(_Range&& __range) const + -> __well_formed_sequence_sender auto { + return make_sequence_expr( + __decayed_tuple{ + trampoline_t{} + , static_cast<_Range&&>(__range)}); + } + template + requires __decay_copyable<_Range> && __decay_copyable<_Scheduler> + auto operator()(_Scheduler&& __scheduler, _Range&& __range) const + -> __well_formed_sequence_sender auto { + return make_sequence_expr( + __decayed_tuple<_Scheduler, _Range>{ + static_cast<_Scheduler&&>(__scheduler) + , static_cast<_Range&&>(__range)}); } using __completion_sigs = completion_signatures; - template - using __item_sender_t = __result_of< - exec::sequence, - schedule_result_t, - __sender_t<__data_of<_Sequence>> - >; + template + using __scheduler_t = stdexec::__if_c< + stdexec::__decays_to> + , trampoline_scheduler + , __scheduler_of_t<_Data>>; template - using _NextReceiver = stdexec::__t<__next_receiver<__data_of<_Sequence>, __id<_Receiver>>>; + using _NextReceiver = stdexec::__t< + __next_receiver< + __scheduler_t<__data_of<_Sequence>> + , __range_of_t<__data_of<_Sequence>> + , __id<_Receiver>>>; + + template + using __item_sender_t = __result_of< + exec::sequence, + schedule_result_t<__scheduler_t<__data_of<_Sequence>>&>, + __sender_t<__range_of_t<__data_of<_Sequence>>> + >; template using _NextSender = next_sender_of_t<_Receiver, __item_sender_t<_Sequence>>; @@ -198,13 +250,14 @@ namespace exec { return __sexpr_apply(static_cast<_SeqExpr&&>(__seq), __subscribe_fn<_Receiver>{__rcvr}); } - static auto get_completion_signatures(__ignore, __ignore = {}) noexcept - -> completion_signatures { + template _Sequence> + static auto get_completion_signatures(_Sequence&&, __ignore = {}) noexcept + -> __completion_sigs { return {}; } template _Sequence> - static auto get_item_types(_Sequence&&, __ignore) noexcept // + static auto get_item_types(_Sequence&&, __ignore) noexcept -> item_types<__item_sender_t<_Sequence>> { return {}; } diff --git a/test/exec/sequence/test_iterate.cpp b/test/exec/sequence/test_iterate.cpp index 5482b561f..103ede111 100644 --- a/test/exec/sequence/test_iterate.cpp +++ b/test/exec/sequence/test_iterate.cpp @@ -16,6 +16,7 @@ */ #include "exec/sequence/iterate.hpp" +#include "exec/sequence_senders.hpp" #include "stdexec/execution.hpp" #if STDEXEC_HAS_STD_RANGES() @@ -114,7 +115,7 @@ namespace { struct my_domain { template Sender, class _Env> auto transform_sender(Sender&& sender, _Env&&) const noexcept { - auto range = + auto [scheduler, range] = stdexec::__sexpr_apply(std::forward(sender), stdexec::__detail::__get_data{}); auto sum = std::accumulate(std::ranges::begin(range), std::ranges::end(range), 0); return stdexec::just(sum + 1); From 84b041f4ab9d722d5958760034108e849fe6bacf Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 18 Oct 2025 21:43:50 +0000 Subject: [PATCH 18/39] add test_scheduler and marble and notification --- include/exec/sequence/marbles.hpp | 812 +++++++++++++++ include/exec/sequence/notification.hpp | 243 +++++ include/exec/sequence/test_scheduler.hpp | 957 ++++++++++++++++++ test/exec/CMakeLists.txt | 2 + test/exec/sequence/test_marbles.cpp | 183 ++++ test/exec/sequence/test_merge_each.cpp | 227 +++-- .../sequence/test_merge_each_threaded.cpp | 2 - test/exec/sequence/test_test_scheduler.cpp | 268 +++++ test/test_common/sequences.hpp | 217 ++++ 9 files changed, 2818 insertions(+), 93 deletions(-) create mode 100644 include/exec/sequence/marbles.hpp create mode 100644 include/exec/sequence/notification.hpp create mode 100644 include/exec/sequence/test_scheduler.hpp create mode 100644 test/exec/sequence/test_marbles.cpp create mode 100644 test/exec/sequence/test_test_scheduler.cpp create mode 100644 test/test_common/sequences.hpp diff --git a/include/exec/sequence/marbles.hpp b/include/exec/sequence/marbles.hpp new file mode 100644 index 000000000..64f284544 --- /dev/null +++ b/include/exec/sequence/marbles.hpp @@ -0,0 +1,812 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +#include "../../stdexec/concepts.hpp" +#include "../../stdexec/execution.hpp" +#include "../sequence_senders.hpp" + +#include "./notification.hpp" +#include "exec/timed_scheduler.hpp" +#include "stdexec/__detail/__completion_signatures.hpp" +#include "stdexec/__detail/__config.hpp" +#include "stdexec/__detail/__execution_fwd.hpp" +#include "stdexec/__detail/__operation_states.hpp" +#include "stdexec/__detail/__sender_introspection.hpp" +#include "stdexec/__detail/__tuple.hpp" + +#include +#include +#include +#include + +namespace exec { + namespace __marbles { + using namespace stdexec; + using namespace std::chrono_literals; + + // + // marble_t<_Clock> represents a signal for a sequence + // sender and the frame at which the signal occurs/occured + // + // a marble diagram is a string that is parsed into a + // vector> + // + // Example: + // this marble diagram + // "--a--b--c|" + // is equivalent to this set of marble_t + // marble_t{2ms, ex::set_value, 'a'}, + // marble_t{5ms, ex::set_value, 'b'}, + // marble_t{8ms, ex::set_value, 'c'}, + // marble_t{8ms, sequence_end} + // which is displayed as + // set_value('a')@2ms, set_value('b')@5ms, + // set_value('c')@8ms, sequence_end()@8ms + // + // Diagram reference: + // Time: + // ' ' indicates that 0ms has elapsed - used to line up diagrams in a visually pleasing manner + // '-' indicates that 1ms has elapsed + // ' 0-9+(ms|s|m) ' + // indicates elapsed time at that point in the diagram that is equal to the specified number + // of ms - milliseconds, s - seconds, m - minutes + // NOTE: must have a preceding and following space to disambiguate from values + // '(' begins a group of signals that all occur on the frame that the group begins on + // ')' ends a group of signals that all occur on the frame that the group begins on + // Value: + // '0'-'9' 'a'-'z' 'A'-'Z' + // indicates that a value in the sequence completes with set_value( char ) + // '#' indicates that a value in the sequence completes with set_error(error_code(interrupted)) + // '.' indicates that a value in the sequence completes with set_stopped() + // Sequence: + // '=' indicates connect() on the sequence sender + // '^' indicates start() on the sequence operation + // '|' indicates that the sequence completes with set_value() + // '$' indicates that the sequence completes with set_stopped() + // '?' indicates that request_stop() was sent to the sequence from an external source + // + // record_marbles() will record a set of marbles from the signals of the specified sequence sender + // + + + // __value_t is a hammer to stop char from being treated like an integer + + struct __value_t { + char __c_; + + operator char() noexcept { + return __c_; + } + + friend auto operator==( + const __value_t& __lhs, + const __value_t& __rhs) noexcept -> bool { + return __lhs.__c_ == __rhs.__c_; + } + + friend std::string to_string(const __value_t& __self) noexcept { + using std::to_string; + return "'" + std::string{1, __self.__c_} + "'"; + } + }; + + enum class marble_selector_t { + uninitialized, + frame_only, + notification, + sequence_start, + sequence_connect, + sequence_value, + sequence_error, + sequence_stopped, + request_stop + }; + + struct sequence_start_t { + operator marble_selector_t() const noexcept { return marble_selector_t::sequence_start; } + }; + static constexpr inline sequence_start_t sequence_start; + + struct sequence_connect_t { + operator marble_selector_t() const noexcept { return marble_selector_t::sequence_connect; } + }; + static constexpr inline sequence_connect_t sequence_connect; + + struct sequence_end_t { + operator marble_selector_t() const noexcept { return marble_selector_t::sequence_value; } + }; + static constexpr inline sequence_end_t sequence_end; + + struct sequence_error_t { + operator marble_selector_t() const noexcept { return marble_selector_t::sequence_error; } + }; + static constexpr inline sequence_error_t sequence_error; + + struct sequence_stopped_t { + operator marble_selector_t() const noexcept { return marble_selector_t::sequence_stopped; } + }; + static constexpr inline sequence_stopped_t sequence_stopped; + + struct request_stop_t { + operator marble_selector_t() const noexcept { return marble_selector_t::request_stop; } + }; + static constexpr inline request_stop_t request_stop; + + using __completion_signatures_t = completion_signatures< + set_value_t(__value_t) + , set_error_t(std::error_code) + , set_error_t(std::exception_ptr) + , set_stopped_t()>; + + template + struct marble_t { + using __frame_t = typename _Clock::time_point; + using __duration_t = typename _Clock::duration; + using __notification_t = notification_t<__completion_signatures_t>; + using __marble_sender_t = typename __notification_t::__notification_sender_t; + + using selector = marble_selector_t; + + __frame_t __at_; + selector __selector_; + std::optional<__notification_t> __notification_; + + marble_t(__frame_t __at, set_error_t __tag, std::error_code __error) noexcept + : __at_{__at} + , __selector_{selector::notification} + , __notification_{} { + __notification_.emplace(__tag, __error); + } + marble_t(__frame_t __at, set_error_t __tag, std::exception_ptr __ex) noexcept + : __at_{__at} + , __selector_{selector::notification} + , __notification_{} { + __notification_.emplace(__tag, __ex); + } + marble_t(__frame_t __at, set_value_t __tag, char __c) noexcept + : __at_{__at} + , __selector_{selector::notification} + , __notification_{} { + __notification_.emplace(__tag, __value_t{__c}); + } + marble_t(__frame_t __at, set_value_t __tag, __value_t __v) noexcept + : __at_{__at} + , __selector_{selector::notification} + , __notification_{} { + __notification_.emplace(__tag, __v); + } + marble_t(__frame_t __at, set_stopped_t __tag) noexcept + : __at_{__at} + , __selector_{selector::notification} + , __notification_{} { + __notification_.emplace(__tag); + } + marble_t(__frame_t __at, sequence_start_t __tag) noexcept + : __at_{__at} + , __selector_{__tag} + , __notification_{} { + } + marble_t(__frame_t __at, sequence_connect_t __tag) noexcept + : __at_{__at} + , __selector_{__tag} + , __notification_{} { + } + marble_t(__frame_t __at, sequence_end_t __tag) noexcept + : __at_{__at} + , __selector_{__tag} + , __notification_{} { + } + marble_t(__frame_t __at, sequence_error_t __tag) noexcept + : __at_{__at} + , __selector_{__tag} + , __notification_{} { + } + marble_t(__frame_t __at, sequence_stopped_t __tag) noexcept + : __at_{__at} + , __selector_{__tag} + , __notification_{} { + } + marble_t(__frame_t __at, request_stop_t __tag) noexcept + : __at_{__at} + , __selector_{__tag} + , __notification_{} { + } + marble_t(__frame_t __at) noexcept + : __at_{__at} + , __selector_{selector::frame_only} + , __notification_{} { + } + + template + void visit(_Fn&& __fn) noexcept { + if (__notification_.has_value()) { + __notification_->visit(static_cast<_Fn&&>(__fn)); + } + } + + template + void visit_receiver(_Receiver&& __receiver) noexcept { + if (__selector_ == selector::notification) { + __notification_->visit_receiver(static_cast<_Receiver&&>(__receiver)); + } else { + stdexec::set_stopped(static_cast<_Receiver&&>(__receiver)); + } + } + + template + void visit_sequence_receiver(_Receiver&& __receiver) noexcept { + switch (__selector_) { + case selector::sequence_value: { + stdexec::set_value(static_cast<_Receiver&&>(__receiver)); + break; + } + case selector::sequence_error: { + stdexec::set_error(static_cast<_Receiver&&>(__receiver), std::exception_ptr{}); + break; + } + case selector::notification: { + if (value_notification()) { + stdexec::set_value(static_cast<_Receiver&&>(__receiver)); + break; + } + } + [[fallthrough]]; + case selector::request_stop: + [[fallthrough]]; + case selector::sequence_stopped: + [[fallthrough]]; + case selector::sequence_connect: + [[fallthrough]]; + case selector::sequence_start: + [[fallthrough]]; + case selector::frame_only: + [[fallthrough]]; + default: { + stdexec::set_stopped(static_cast<_Receiver&&>(__receiver)); + break; + } + }; + } + + [[nodiscard]] auto visit_sender() noexcept -> __marble_sender_t { + return __notification_->visit_sender(); + } + + [[nodiscard]] bool sequence_end() const noexcept { + return __selector_ == selector::sequence_value; + } + [[nodiscard]] bool sequence_error() const noexcept { + return __selector_ == selector::sequence_error; + } + [[nodiscard]] bool sequence_stopped() const noexcept { + return __selector_ == selector::sequence_stopped; + } + [[nodiscard]] bool request_stop() const noexcept { + return __selector_ == selector::request_stop; + } + [[nodiscard]] bool value_notification() const noexcept { + return __notification_.has_value() && __notification_->value(); + } + [[nodiscard]] bool error_notification() const noexcept { + return __notification_.has_value() && __notification_->error(); + } + [[nodiscard]] bool stopped_notification() const noexcept { + return __notification_.has_value() && __notification_->stopped(); + } + [[nodiscard]] __frame_t frame() const noexcept { + return __at_; + } + __frame_t shift_frame_by(__duration_t __by) noexcept { + __frame_t __old_frame = __at_; + __at_ += __by; + return __old_frame; + } + __frame_t set_origin_frame(__frame_t __origin) noexcept { + __frame_t __old_frame = __at_; + __at_ += __origin.time_since_epoch(); + return __old_frame; + } + + friend auto operator==( + const marble_t& __lhs, + const marble_t& __rhs) noexcept -> bool { + return std::chrono::duration_cast(__lhs.__at_.time_since_epoch()) + == std::chrono::duration_cast(__rhs.__at_.time_since_epoch()) + && __lhs.__selector_ == __rhs.__selector_ + && __lhs.__notification_.has_value() == __rhs.__notification_.has_value() + && (__lhs.__notification_.has_value() && __rhs.__notification_.has_value() + ? __lhs.__notification_.value() == __rhs.__notification_.value() + : true); + } + + friend std::string to_string(const marble_t& __self) noexcept { + using std::to_string; + std::string __result; + switch (__self.__selector_) { + case selector::frame_only: { + __result = "frame"; + break; + } + case selector::notification: { + __result = to_string(__self.__notification_.value()); + break; + } + case selector::request_stop: { + __result = to_string(__marbles::request_stop) + "()"; + break; + } + case selector::sequence_start: { + __result = to_string(__marbles::sequence_start) + "()"; + break; + } + case selector::sequence_connect: { + __result = to_string(__marbles::sequence_connect) + "()"; + break; + } + case selector::sequence_value: { + __result = to_string(__marbles::sequence_end) + "()"; + break; + } + case selector::sequence_error: { + __result = to_string(__marbles::sequence_error) + "()"; + break; + } + case selector::sequence_stopped: { + __result = to_string(__marbles::sequence_stopped) + "()"; + break; + } + default: { + return {"uninitialized-marble"}; + } + }; + return + __result + + "@" + + to_string(std::chrono::duration_cast(__self.__at_.time_since_epoch()).count()) + + "ms"; + } + }; + + struct get_marbles_from_t { + + template + constexpr auto operator()(_Clock __clock, __mstring<_Len> __diagram) const noexcept + -> std::vector> { + using __frame_t = typename _Clock::time_point; + using __duration_t = typename _Clock::duration; + + constexpr auto __make_span = [](__mstring<_LenB>&& __string) noexcept { + return std::span{__string.__what_, _LenB - 1}; + }; + + std::vector> __marbles; + __frame_t __group_start_frame{-1ms}; + __frame_t __frame = __clock.now(); + auto __whole = __make_span(std::move(__diagram)); + auto __remaining = __whole; + auto __consume_first = [&__remaining](std::size_t __skip) noexcept { + __remaining = __remaining.subspan(__skip); + }; + auto __push = [&](auto __tag, auto... __args) noexcept { + __marbles.emplace_back( + __group_start_frame == __frame_t{-1ms} ? __frame : __group_start_frame + , __tag, __args...); + }; + while(!__remaining.empty()) { + __frame_t __next_frame{__frame}; + auto __advance_frame_by = [&__next_frame, &__group_start_frame](__duration_t __by) noexcept { + __next_frame += __group_start_frame == __frame_t{-1ms} + ? __by + : 0ms; + }; + switch (__remaining.front()) { + case '-': { + __advance_frame_by(1ms); + __consume_first(1); + break; + } + case '(': { + __group_start_frame = __frame; + __consume_first(1); + break; + } + case ')': { + __group_start_frame = __frame_t{-1ms}; + __advance_frame_by(1ms); + __consume_first(1); + break; + } + case '|': { + __push(sequence_end); + __consume_first(1); + break; + } + case '=': { + __push(sequence_connect); + __consume_first(1); + break; + } + case '^': { + __push(sequence_start); + __consume_first(1); + break; + } + case '$': { + __push(sequence_stopped); + __consume_first(1); + break; + } + case '?': { + __push(request_stop); + __consume_first(1); + break; + } + case '#': { + __push(set_error, std::make_error_code(std::errc::interrupted)); + __consume_first(1); + break; + } + case '.': { + __push(set_stopped); + __consume_first(1); + break; + } + default: { + long __consumed_in_default = 0; + if (__whole.begin() == __remaining.begin() || !!std::isspace(__remaining.front())) { + if (!!std::isspace(__remaining.front())) { + __consume_first(1); + ++__consumed_in_default; + } + // try to consume a duration at first char or after ' ' char. + if (!!std::isdigit(__remaining.front())) { + auto __valid_duration_suffix = [](auto c) noexcept { + return c == 'm' || c == 's'; + }; + auto __suffix_begin = std::ranges::find_if(__remaining, __valid_duration_suffix); + bool __all_digits = std::all_of( + __remaining.begin() + , __suffix_begin + , [](auto c){ return std::isdigit(c); }); + if ( + __suffix_begin != __remaining.end() + && __suffix_begin - __remaining.begin() > 0 + && __all_digits) { + long __to_consume = __suffix_begin - __remaining.begin(); + long __duration = std::atol(__remaining.data()); + if (std::ranges::equal(__remaining.subspan(__to_consume, 3), __make_span("ms "_mstr))) { + __to_consume += 2; + } else if (std::ranges::equal(__remaining.subspan(__to_consume, 2), __make_span("s "_mstr))) { + __duration *= 1000; + __to_consume += 1; + } else if (std::ranges::equal(__remaining.subspan(__to_consume, 2), __make_span("m "_mstr))) { + __duration = __duration * 1000 * 60; + __to_consume += 1; + } else { + __duration = -1; + __to_consume = 0; + //fallthrough + } + if (__duration >= 0 && __to_consume > 0) { + __advance_frame_by(std::chrono::milliseconds(__duration)); + __consume_first(__to_consume); + __consumed_in_default += __to_consume; + break; + } + } + } + } + if (!!std::isalnum(__remaining.front())) { + __advance_frame_by(1ms); + __push(set_value, __remaining.front()); + __consume_first(1); + ++__consumed_in_default; + break; + } + if (__consumed_in_default == 0) { + // parsing error + return __marbles; + } + break; + } + }; + __frame = __next_frame; + } + return __marbles; + } + }; + + template + struct __value_receiver { + using __t = __value_receiver; + using __id = __value_receiver; + using receiver_concept = stdexec::receiver_t; + + _Clock __clock_; + std::vector>* __recording_; + _Receiver* __receiver_; + + template + void set_value(_Args&&... __args) noexcept { + __recording_->emplace_back(__clock_.now(), stdexec::set_value, static_cast<_Args&&>(__args)...); + stdexec::set_value(static_cast<_Receiver>(*__receiver_)); + } + + template + void set_error(_Error&& __error) noexcept { + __recording_->emplace_back(__clock_.now(), stdexec::set_error, static_cast<_Error&&>(__error)); + stdexec::set_stopped(static_cast<_Receiver>(*__receiver_)); + } + + void set_stopped() noexcept { + __recording_->emplace_back(__clock_.now(), stdexec::set_stopped); + stdexec::set_stopped(static_cast<_Receiver>(*__receiver_)); + } + + auto get_env() const noexcept -> env_of_t<_Receiver> { + return stdexec::get_env(*__receiver_); + } + }; + + template + struct __value_operation { + using _Receiver = stdexec::__t<_ReceiverId>; + + struct __t { + using __id = __value_operation; + + using __receiver_t = __value_receiver<_Receiver, _Clock>; + + _Receiver __receiver_; + _Clock __clock_; + std::vector>* __recording_; + stdexec::connect_result_t<_Value, __receiver_t> __op_; + + __t(_Value&& __value, _Receiver&& __receiver, _Clock __clock, std::vector>* __recording) noexcept + : __receiver_{static_cast<_Receiver&&>(__receiver)} + , __clock_{__clock} + , __recording_(__recording) + , __op_{stdexec::connect(static_cast<_Value&&>(__value), __receiver_t{__clock_, __recording_, &__receiver_})} + {} + + void start() & noexcept { + stdexec::start(__op_); + } + }; + }; + + template + struct __value_sender { + struct __t { + using __id = __value_sender; + using sender_concept = stdexec::sender_t; + + template + using __value_operation_t = stdexec::__t<__value_operation<_Value, stdexec::__id<_Receiver>, _Clock>>; + + _Clock __clock_; + std::vector>* __recording_; + _Value __value_; + + template _Self, class... _Env> + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept + -> stdexec::completion_signatures { + return {}; + } + + template _Self, receiver _Receiver> + static auto connect(_Self&& __self, _Receiver&& __rcvr) + noexcept(__nothrow_move_constructible<_Receiver>) { + return __value_operation_t<_Receiver>{static_cast<_Value&&>(__self.__value_), + static_cast<_Receiver&&>(__rcvr), + __self.__clock_, + __self.__recording_}; + } + }; + }; + + template + struct __receiver { + using __t = __receiver; + using __id = __receiver; + using receiver_concept = stdexec::receiver_t; + + template + using __value_sender_t = stdexec::__t<__value_sender<_Value, _Clock>>; + + _Clock __clock_; + std::vector>* __recording_; + _Receiver* __receiver_; + + using __receiver_t = __value_receiver<_Receiver, _Clock>; + + template + auto set_next(_Value&& __value) + noexcept + -> next_sender auto { + return __value_sender_t<_Value>{ + __clock_, + __recording_, + static_cast<_Value&&>(__value) + }; + } + + void set_value() noexcept { + __recording_->emplace_back(__clock_.now(), sequence_end); + stdexec::set_value(static_cast<_Receiver&&>(*__receiver_)); + } + + template + void set_error(_Error&& ) noexcept { + __recording_->emplace_back(__clock_.now(), sequence_error); + stdexec::set_value(static_cast<_Receiver&&>(*__receiver_)); + } + + void set_stopped() noexcept { + __recording_->emplace_back(__clock_.now(), sequence_stopped); + stdexec::set_value(static_cast<_Receiver&&>(*__receiver_)); + } + + auto get_env() const noexcept -> env_of_t<_Receiver> { + return stdexec::get_env(*__receiver_); + } + }; + + template + struct __operation { + using _Receiver = stdexec::__t<_ReceiverId>; + + struct __t { + using __id = __operation; + + using __receiver_t = __receiver<_Receiver, _Clock>; + + _Receiver __receiver_; + _Clock __clock_; + std::vector>* __recording_; + exec::subscribe_result_t<_Sequence, __receiver_t> __op_; + + __t(_Sequence&& __sequence, _Receiver&& __receiver, _Clock __clock, std::vector>* __recording) noexcept + : __receiver_{static_cast<_Receiver&&>(__receiver)} + , __clock_{__clock} + , __recording_(__recording) + , __op_{exec::subscribe( + static_cast<_Sequence&&>(__sequence) + , __receiver_t{__clock_, __recording_, &__receiver_})} + {} + + void start() & noexcept { + __recording_->emplace_back(__clock_.now(), sequence_start); + stdexec::start(__op_); + } + }; + }; + + template + struct __connect_fn { + _Receiver __rcvr_; + + using __receiver_id_t = __id<_Receiver>; + + template + using __operation_t = __t<__operation<_Child, __receiver_id_t, _Clock>>; + + template + auto operator()(__ignore, _Data&& __data, _Child&& __child) + noexcept(__nothrow_constructible_from< + __operation_t<_Child> + , _Child + , _Receiver + , _Clock + , std::vector>*>) + -> __operation_t<_Child> { + auto [__recording, __clock] = static_cast<_Data&&>(__data); + __recording->emplace_back(__clock.now(), sequence_connect); + return {static_cast<_Child&&>(__child), + static_cast<_Receiver&&>(__rcvr_), + __clock, + __recording}; + } + }; + + struct record_marbles_t { + template + auto operator()(std::vector>* __recording, _Clock __clock, _Sequence&& __sequence) const + {//-> __well_formed_sender auto { + auto __domain = __get_early_domain(static_cast<_Sequence&&>(__sequence)); + return transform_sender( + __domain, __make_sexpr( + __decayed_tuple>*, _Clock>{__recording, __clock} + , static_cast<_Sequence&&>(__sequence))); + } + template + std::vector> operator()(_Clock __clock, _Sequence&& __sequence) const noexcept { + std::vector> __recording; + auto __recorder = (*this)(&__recording, __clock, static_cast<_Sequence&&>(__sequence)); + stdexec::sync_wait(__recorder); + return __recording; + } + }; + + struct __record_marbles_impl : __sexpr_defaults { + + template + using __clock_of_t = __mapply<__q<__mback>, __data_of>; + + static constexpr auto get_completion_signatures = [] + _Sender, class... _Env> + (_Sender&&, _Env&&...) noexcept + -> completion_signatures { + return {}; + }; + + static constexpr auto connect = [] + _Self, receiver _Receiver> + (_Self&& __self, _Receiver&& __rcvr) + noexcept(__nothrow_callable<__sexpr_apply_t, _Self, __connect_fn<__clock_of_t<_Self>, _Receiver>>) + -> __call_result_t<__sexpr_apply_t, _Self, __connect_fn<__clock_of_t<_Self>, _Receiver>> { + return __sexpr_apply( + static_cast<_Self&&>(__self) + , __connect_fn<__clock_of_t<_Self>, _Receiver>{static_cast<_Receiver&&>(__rcvr)}); + }; + }; + + } // __marbles + + using sequence_start_t = __marbles::sequence_start_t; + static constexpr inline auto sequence_start = sequence_start_t{}; + + using sequence_connect_t = __marbles::sequence_connect_t; + static constexpr inline auto sequence_connect = sequence_connect_t{}; + + using sequence_end_t = __marbles::sequence_end_t; + static constexpr inline auto sequence_end = sequence_end_t{}; + + using sequence_error_t = __marbles::sequence_error_t; + static constexpr inline auto sequence_error = sequence_error_t{}; + + using sequence_stopped_t = __marbles::sequence_stopped_t; + static constexpr inline auto sequence_stopped = sequence_stopped_t{}; + + using request_stop_t = __marbles::request_stop_t; + static constexpr inline auto request_stop = request_stop_t{}; + + template + using marble_t = __marbles::marble_t<_Clock>; + + using get_marbles_from_t = __marbles::get_marbles_from_t; + + static constexpr inline auto get_marbles_from = get_marbles_from_t{}; + + using record_marbles_t = __marbles::record_marbles_t; + + static constexpr inline auto record_marbles = record_marbles_t{}; + + namespace __marbles { + + } +} // namespace exec + +namespace stdexec { + template <> + struct __sexpr_impl + : exec::__marbles::__record_marbles_impl { }; +} // namespace stdexec diff --git a/include/exec/sequence/notification.hpp b/include/exec/sequence/notification.hpp new file mode 100644 index 000000000..793f0bd4a --- /dev/null +++ b/include/exec/sequence/notification.hpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "../../stdexec/concepts.hpp" +#include "../../stdexec/execution.hpp" +#include "stdexec/__detail/__concepts.hpp" +#include "stdexec/__detail/__config.hpp" +#include "stdexec/__detail/__tuple.hpp" +#include + +namespace exec { + namespace __notification { + using namespace stdexec; + + // + // notification_t provides storage for any one of the + // completions in the specified completion_signatures<> + // + // notification_t can be compared, visited by a receiver + // to emit the stored signal, and provide a sender that will + // complete with the stored signal + // + + template + using __nothrow_decay_copyable_and_move_constructible_t = __mbool<( + (__nothrow_decay_copyable<_Ts> && __nothrow_move_constructible<__decay_t<_Ts>>) && ...)>; + + template + using __as_rvalues = set_value_t (*)(__decay_t...); + + template + using __as_error = set_error_t (*)(E...); + + // Here we convert all set_value(Args...) to set_value(__decay_t...). Note, we keep all + // error types as they are and unconditionally add set_stopped(). The indirection through the + // __completions_fn is to avoid a pack expansion bug in nvc++. + struct __completions_fn { + template + using __all_value_args_nothrow_decay_copyable = __value_types_t< + _CompletionSignatures, + __qq<__nothrow_decay_copyable_and_move_constructible_t>, + __qq<__mand_t> + >; + + template + using __f = __mtry_q<__concat_completion_signatures>::__f< + __eptr_completion_if_t<__all_value_args_nothrow_decay_copyable<_CompletionSignatures>>, + completion_signatures, + __transform_completion_signatures< + _CompletionSignatures, + __as_rvalues, + __as_error, + set_stopped_t (*)(), + __completion_signature_ptrs + > + >; + }; + + template + using __notification_storage_t = __for_each_completion_signature< + __minvoke<__completions_fn, _CompletionSignatures>, + __decayed_tuple, + std::variant + >; + + template + struct __notification_sender; + + template + struct notification_t { + using __notification_t = __notification_storage_t<_CompletionSignatures>; + using __notification_sender_t = stdexec::__t<__notification_sender<_CompletionSignatures>>; + + __notification_t __notification_{}; + + template + using __tag_of_t = stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Notification)>; + + template + notification_t(_Tag __tag, _Args&&... __args) + noexcept( + noexcept( + __notification_.template emplace<__decayed_tuple<_Tag, STDEXEC_REMOVE_REFERENCE(_Args)...>>( + __tag, + static_cast<_Args&&>(__args)...))) { + __notification_.template emplace<__decayed_tuple<_Tag, STDEXEC_REMOVE_REFERENCE(_Args)...>>(__tag, static_cast<_Args&&>(__args)...); + } + + template + auto visit(_Fn&& __fn) const noexcept { + return std::visit( + [&__fn](auto&& __tuple) noexcept { + return __tuple.apply([&__fn](auto __tag, auto&&... __args) noexcept { + return static_cast<_Fn&&>(__fn)(__tag, __args...); + } + , __tuple); + } + , __notification_); + } + template + void visit_receiver(_Receiver&& __receiver) noexcept { + std::visit( + [&__receiver](auto&& __tuple) noexcept { + __tuple.apply([&__receiver](auto __tag, auto&&... __args) noexcept { + __tag(static_cast<_Receiver&&>(__receiver), static_cast(__args)...); + } + , static_cast(__tuple)); + } + , static_cast<__notification_t&&>(__notification_)); + } + auto visit_sender() noexcept -> __notification_sender_t; + + [[nodiscard]] bool value() const noexcept { + return std::visit( + [](const _Tuple&) noexcept { + return stdexec::__decays_to>; + } + , __notification_); + } + [[nodiscard]] bool error() const noexcept { + return std::visit( + [](const _Tuple&) noexcept { + return stdexec::__decays_to>; + } + , __notification_); + } + [[nodiscard]] bool stopped() const noexcept { + return std::visit( + [](const _Tuple&) noexcept { + return stdexec::__decays_to>; + } + , __notification_); + } + + friend auto operator==( + const notification_t& __lhs, + const notification_t& __rhs) noexcept -> bool { + return std::visit( + [](const _Lhs& __lhs, const _Rhs& __rhs) noexcept { + if constexpr ( + !std::same_as<__tag_of_t<_Lhs>, __tag_of_t<_Rhs>> + || stdexec::__v> != stdexec::__v>) { + return false; + } else { + return __lhs.apply([&__rhs](_LTag, const _LArgs&... __l_args){ + return __rhs.apply([&](_RTag, const _RArgs&... __r_args){ + if constexpr ((std::equality_comparable_with && ... && true)) { + return ((__l_args == __r_args) && ... && true); + } else { + return false; + } + } + , __rhs); + } + , __lhs); + } + } + , __lhs.__notification_, __rhs.__notification_); + } + + friend std::string to_string(const notification_t& __self) noexcept { + using std::to_string; + return __self.visit([](auto __tag, const auto&... __args){ + int count = 0; + return to_string(__tag) + "(" + (((count++ > 0 ? ", " : "") + to_string(__args)) + ... + std::string{}) + ")"; + }); + } + }; + + template + struct __notification_op { + using _Receiver = stdexec::__t<_ReceiverId>; + using __notification_t = notification_t<_CompletionSignatures>; + + struct __t { + using __id = __notification_op; + + _Receiver __receiver_; + __notification_t* __notification_; + + void start() & noexcept { + __notification_->visit_receiver(static_cast<_Receiver&&>(__receiver_)); + } + }; + }; + + template + struct __notification_sender { + using __notification_t = notification_t<_CompletionSignatures>; + struct __t { + using __id = __notification_sender; + using sender_concept = stdexec::sender_t; + + template + using __notification_op_t = stdexec::__t<__notification_op<_ReceiverId, _CompletionSignatures>>; + + __notification_t* __notification_; + + template _Self, class... _Env> + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept + -> _CompletionSignatures { + return {}; + } + + template _Self, receiver _Receiver> + static auto connect(_Self&& __self, _Receiver&& __rcvr) + noexcept(__nothrow_move_constructible<_Receiver>) + -> __notification_op_t> { + return {static_cast<_Receiver&&>(__rcvr), + __self.__notification_}; + } + }; + }; + + template + auto notification_t<_CompletionSignatures>::visit_sender() noexcept + -> notification_t<_CompletionSignatures>::__notification_sender_t { + return {this}; + } + + } // namespace __notification + template + using notification_t = __notification::notification_t<_CompletionSignatures>; + + namespace __notification { + + } // namespace __notification +} // namespace exec diff --git a/include/exec/sequence/test_scheduler.hpp b/include/exec/sequence/test_scheduler.hpp new file mode 100644 index 000000000..960ab0a77 --- /dev/null +++ b/include/exec/sequence/test_scheduler.hpp @@ -0,0 +1,957 @@ +/* + * Copyright (c) 2024 Maikel Nadolski + * Copyright (c) 2024 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../timed_scheduler.hpp" +#include "./marbles.hpp" +#include "../__detail/intrusive_heap.hpp" + +#include "../../stdexec/__detail/__intrusive_mpsc_queue.hpp" +#include "exec/finally.hpp" +#include "exec/sequence.hpp" +#include "exec/sequence_senders.hpp" +#include "exec/variant_sender.hpp" +#include "stdexec/__detail/__completion_signatures.hpp" +#include "stdexec/__detail/__concepts.hpp" +#include "stdexec/__detail/__execution_fwd.hpp" +#include "stdexec/__detail/__meta.hpp" +#include "stdexec/__detail/__receivers.hpp" +#include "stdexec/__detail/__senders.hpp" +#include "stdexec/__detail/__senders_core.hpp" +#include "stdexec/__detail/__stop_token.hpp" +#include "stdexec/__detail/__transform_completion_signatures.hpp" +#include "stdexec/__detail/__type_traits.hpp" + +#include +#include +#include + +namespace exec { + class test_scheduler; + + // + // test_context implements a time-scheduler with additional test features. + // + // test_context is used to build tests using marble sequence senders + // and marble recorders + // + // test_clock_context & test_clock are used to represent time for the tests + // + // test_scheduler is used to queue tasks on the test_context at virtual-time points + // + // One or more __test_sequence(s) are constructed from marble + // diagrams and each schedules marbles on the test_scheduler + // when connected and started + // + // Once one or more __test_sequence(s) have been composed into an expression, + // the test_context is used to record the expression results as a + // set of marbles. To support the testing of infinite sequences + // the recording will be requested to stop at a default of 1000ms from + // start() if the expression has not completed. + // + // The expression result marbles are then compared to an expected + // set of marbles generated from a separate marble diagram + // + // An example of usage: + // + // TEST_CASE( + // "test_scheduler - test_context marble-sequence never", + // "[sequence_senders][test_scheduler]") { + // test_context __test{}; + // auto __clock = __test.get_clock(); + // CHECK(test_clock::time_point{0ms} == __clock.now()); + // + // // a sequence that will produce '0' at 1ms from start() + // // and then never complete + // auto __sequence = __test.get_marble_sequence_from( + // " -0-"_mstr); + // + // // the set of marbles for a sequence that contains '5' + // // at 1ms from start() and then is externally stopped + // // after 1000ms have elapsed since start() + // auto expected = get_marbles_from(__clock, + // "=^-5 998ms $"_mstr); + // + // // record an expression that is expected to turn '0' to '5' + // auto actual = __test.get_marbles_from( + // __sequence + // | then_each([](char c){ return c+5; })); + // + // CHECK(test_clock::time_point{1000ms} == __clock.now()); + // CAPTURE(__sequence.__marbles_); + // CHECK(expected == actual); + // } + // + + struct test_clock_context; + + // + // test_clock and test_clock_context implement a + // manually advanced virtual-time clock + // + // this is used to run tests that depend on time + // without consuming actual real-time + // + // So tests using virtual-time will complete + // fast in real-time + // + + struct test_clock + { + using duration = std::chrono::milliseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + [[maybe_unused]] static const bool is_steady = false; + + const test_clock_context* __context_; + + [[maybe_unused, nodiscard]] time_point now() const noexcept; + }; + + struct test_clock_context + { + using duration = typename test_clock::duration; + using rep = duration::rep; + using period = duration::period; + using time_point = typename test_clock::time_point; + [[maybe_unused]] static const bool is_steady = test_clock::is_steady; + + time_point __now_{}; + + [[maybe_unused, nodiscard]] time_point now() const noexcept { + return __now_; + } + + auto advance_now_to(time_point __new_now) noexcept -> time_point { + time_point __old_now = __now_; + __now_ = __new_now; + return __old_now; + } + auto advance_now_by(duration __by) noexcept -> time_point { + time_point __old_now = __now_; + __now_ += __by; + return __old_now; + } + }; + + inline typename test_clock::time_point test_clock::now() const noexcept { + return __context_->now(); + } + + + namespace _tst_sched { + using namespace stdexec::tags; + + struct test_operation_base { + enum class command_type { + uninitialized, + schedule, + stop + }; + enum class location { + uninitialized, + inert, + in_command_queue, + in_heap + }; + + ~test_operation_base() noexcept { + STDEXEC_ASSERT(location_ == location::inert); + } + + test_operation_base( + void (*set_value)(test_operation_base*) noexcept, + command_type command = command_type::schedule) noexcept + : command_{command} + , set_value_{set_value} { + } + + std::atomic next_{nullptr}; + location location_{location::inert}; + command_type command_; + void (*set_value_)(test_operation_base*) noexcept; + }; + + template + struct when_type { + when_type() = default; + + explicit when_type(Tp tp, std::size_t n = 0) noexcept + : time_point{std::move(tp)} + , counter{n} { + } + + Tp time_point{}; + std::size_t counter{}; + + friend auto operator<(const when_type& lhs, const when_type& rhs) noexcept -> bool { + return lhs.time_point < rhs.time_point + || (!(rhs.time_point < lhs.time_point) && lhs.counter < rhs.counter); + } + }; + + struct alignas(64) test_schedule_operation_base : test_operation_base { + using time_point = test_clock::time_point; + + test_schedule_operation_base( + time_point tp, + void (*set_stopped)(test_operation_base*) noexcept, + void (*set_value)(test_operation_base*) noexcept) noexcept + : test_operation_base{set_value, command_type::schedule} + , time_point_{tp} + , set_stopped_{set_stopped} { + } + + time_point time_point_; + // we increase the when counter to ensure that the heap is stable + // when two operations have the same time_point + // We do so only when the operation is started, not when it is constructed + when_type when_{}; + test_schedule_operation_base* prev_ = nullptr; + test_schedule_operation_base* left_ = nullptr; + test_schedule_operation_base* right_ = nullptr; + void (*set_stopped_)(test_operation_base*) noexcept; + }; + + struct alignas(64) test_stop_operation : test_operation_base { + test_stop_operation( + void (*set_value)(test_operation_base*) noexcept, + test_schedule_operation_base* target) noexcept + : test_operation_base{set_value, command_type::stop} + , target_{target} { + } + + test_schedule_operation_base* target_; + }; + + template + struct test_schedule_at_op { + class __t; + }; + + struct __recording_receiver; + struct __test_sequence; + struct __test_sequence_operation_base; + } // namespace _tst_sched + + class test_context { + private: + static constexpr std::ptrdiff_t context_closed = std::numeric_limits::min() / 2; + public: + using duration = test_clock::duration; + using time_point = test_clock::time_point; + + auto get_scheduler() noexcept -> test_scheduler; + auto get_clock() const noexcept -> test_clock; + auto now() const noexcept -> time_point; + + // parse a marble diagram into a set of marbles + template + auto get_marbles_from(stdexec::__mstring<_Len> __diagram) noexcept + -> std::vector> { + return exec::get_marbles_from(get_clock(), __diagram); + } + + // record the results of a sequence-sender as a set of marbles + template + auto get_marbles_from( + _Sequence&& __sequence + , typename test_clock::duration __stop_after = std::chrono::milliseconds(1000)) noexcept + -> std::vector>; + + + // return a sequence sender that will emit signals specified by the + // set of marbles provided + inline auto get_marble_sequence_from(std::vector> __marbles) noexcept + -> _tst_sched::__test_sequence; + + // parse a marble diagram into a set of marbles and return a sequence + // sender that will emit those marbles + template + auto get_marble_sequence_from(stdexec::__mstring<_Len> __diagram) noexcept + -> _tst_sched::__test_sequence; + + private: + template + friend struct _tst_sched::test_schedule_at_op; + + using command_type = _tst_sched::test_operation_base; + using task_type = _tst_sched::test_schedule_operation_base; + using stop_type = _tst_sched::test_stop_operation; + + void process_command_queue() { + while (command_type* op = command_queue_.pop_front()) { + STDEXEC_ASSERT(op->location_ == command_type::location::in_command_queue); + std::exchange(op->location_, command_type::location::inert); + if (op->command_ == command_type::command_type::schedule) { + auto* task = static_cast(op); + task->when_ = _tst_sched::when_type{task->time_point_, submission_counter_++}; + STDEXEC_ASSERT(task->location_ == command_type::location::inert); + std::exchange(task->location_, command_type::location::in_heap); + heap_.insert(task); + } else { + STDEXEC_ASSERT(op->command_ == command_type::command_type::stop); + auto* stop_op = static_cast(op); + STDEXEC_ASSERT(stop_op->target_->location_ == command_type::location::in_heap); + bool __erased = heap_.erase(stop_op->target_); + std::exchange(stop_op->target_->location_, command_type::location::inert); + if (__erased) { + stop_op->target_->set_stopped_(stop_op->target_); + } + stop_op->set_value_(stop_op); + } + } + } + + void clear_pending() { + STDEXEC_ASSERT(stop_requested_); + std::ptrdiff_t expected = 0; + while (!n_submissions_in_flight_ + .compare_exchange_weak(expected, context_closed, std::memory_order_relaxed) + && expected > 0) { + expected = 0; + } + task_type* op = heap_.front(); + while (op) { + STDEXEC_ASSERT(op->location_ == command_type::location::in_heap); + heap_.pop_front(); + std::exchange(op->location_, command_type::location::inert); + op->set_stopped_(op); + op = heap_.front(); + } + } + + void run() { + while (true) { + process_command_queue(); + task_type* op = heap_.front(); + if (!!op) { + STDEXEC_ASSERT(op->location_ == command_type::location::in_heap); + heap_.pop_front(); + std::exchange(op->location_, command_type::location::inert); + if (__clock_.now() < op->time_point_) { + __clock_.advance_now_to(op->time_point_); + } + op->set_value_(op); + std::exchange(op, nullptr); + } + bool stop_requested = stop_requested_; + ready_ = false; + if (stop_requested) { + clear_pending(); + break; + } + } + } + + void schedule(command_type* op) { + STDEXEC_ASSERT(op->location_ == command_type::location::inert); + std::ptrdiff_t n = n_submissions_in_flight_.fetch_add(1, std::memory_order_relaxed); + if (n < 0) { + if (op->command_ == command_type::command_type::schedule) { + static_cast(op)->set_stopped_(op); + } else { + STDEXEC_ASSERT(op->command_ == command_type::command_type::stop); + static_cast(op)->set_value_(op); + } + n_submissions_in_flight_ + .compare_exchange_strong(n, context_closed, std::memory_order_relaxed); + return; + } + std::exchange(op->location_, command_type::location::in_command_queue); + if (command_queue_.push_back(op)) { + ready_ = true; + } + n_submissions_in_flight_.fetch_sub(1, std::memory_order_relaxed); + } + + void request_stop() { + stop_requested_ = true; + process_command_queue(); + clear_pending(); + } + + friend struct _tst_sched::__recording_receiver; + friend struct _tst_sched::__test_sequence_operation_base; + + stdexec::__intrusive_mpsc_queue<&command_type::next_> command_queue_; + intrusive_heap< + task_type, + _tst_sched::when_type, + &task_type::when_, + &task_type::prev_, + &task_type::left_, + &task_type::right_ + > + heap_; + std::atomic n_submissions_in_flight_{0}; + bool ready_{false}; + bool stop_requested_{false}; + std::size_t submission_counter_{1}; + test_clock_context __clock_; + }; + + namespace _tst_sched { + template + class test_schedule_at_op::__t + : _tst_sched::test_schedule_operation_base { + public: + using __id = test_schedule_at_op; + + __t( + test_context& context, + test_clock::time_point time_point, + Receiver receiver) noexcept + : _tst_sched::test_schedule_operation_base{ + time_point, + [](_tst_sched::test_operation_base* op) noexcept { + auto* self = static_cast<__t*>(op); + int counter = self->ref_count_.fetch_sub(1, std::memory_order_relaxed); + if (counter == 1) { + self->stop_callback_.reset(); + stdexec::set_stopped(std::move(self->receiver_)); + } + }, + [](_tst_sched::test_operation_base* op) noexcept { + auto* self = static_cast<__t*>(op); + int counter = self->ref_count_.fetch_sub(1, std::memory_order_relaxed); + if (counter == 1) { + self->stop_callback_.reset(); + stdexec::set_value(std::move(self->receiver_)); + } + }} + , context_{context} + , receiver_{std::move(receiver)} + , stop_op_{ + [](_tst_sched::test_operation_base* op) noexcept { + auto* stop = static_cast<_tst_sched::test_stop_operation*>(op); + auto* self = static_cast<__t*>(stop->target_); + int counter = self->ref_count_.fetch_sub(1, std::memory_order_relaxed); + if (counter == 1) { + self->stop_callback_.reset(); + stdexec::set_stopped(std::move(self->receiver_)); + } + }, + this} { + } + + void start() & noexcept { + stop_callback_ + .emplace(stdexec::get_stop_token(stdexec::get_env(receiver_)), on_stopped_t{*this}); + int expected = 0; + if (ref_count_.compare_exchange_strong(expected, 1, std::memory_order_relaxed)) { + schedule_this(); + } else { + stop_callback_.reset(); + stdexec::set_stopped(std::move(receiver_)); + } + } + + private: + void schedule_this() noexcept { + context_.schedule(this); + } + + struct on_stopped_t { + __t& self_; + + void operator()() const noexcept { + self_.request_stop(); + } + }; + + using callback_type = typename stdexec::stop_token_of_t< + stdexec::env_of_t + >::template callback_type; + + void request_stop() noexcept { + if (ref_count_.fetch_add(1, std::memory_order_relaxed) == 1) { + context_.schedule(&stop_op_); + } + } + + test_context& context_; + Receiver receiver_; + _tst_sched::test_stop_operation stop_op_; + std::optional stop_callback_; + std::atomic ref_count_{0}; + }; + + } // namespace _tst_sched + + class test_scheduler { + public: + using time_point = test_clock::time_point; + using duration = test_clock::duration; + + class schedule_at_sender { + public: + using sender_concept = stdexec::sender_t; + using completion_signatures = + stdexec::completion_signatures; + + schedule_at_sender( + test_context& context, + test_clock::time_point time_point) noexcept + : context_{&context} + , time_point_{time_point} { + } + + [[nodiscard]] + auto get_env() const noexcept { + return stdexec::prop{ + stdexec::get_completion_scheduler, + test_scheduler{*context_}}; + } + + template + auto connect(Receiver receiver) const & noexcept -> + typename _tst_sched::test_schedule_at_op::__t { + return {*context_, time_point_, std::move(receiver)}; + } + + private: + [[nodiscard]] + auto get_scheduler() const noexcept -> test_scheduler; + + test_context* context_; + test_clock::time_point time_point_; + }; + + explicit test_scheduler(test_context& context) noexcept + : context_{&context} { + } + + [[nodiscard]] + auto now() const noexcept -> time_point { + return context_->now(); + } + + [[nodiscard]] + auto schedule_at(time_point tp) const noexcept -> schedule_at_sender { + return schedule_at_sender{*context_, tp}; + } + + [[nodiscard]] + auto schedule() const noexcept -> schedule_at_sender { + return schedule_at(time_point()); + } + + auto operator==(const test_scheduler&) const noexcept -> bool = default; + + private: + test_context* context_; + }; + + inline auto test_context::get_scheduler() noexcept -> test_scheduler { + return test_scheduler{*this}; + } + + inline auto test_context::get_clock() const noexcept -> test_clock { + return test_clock{&this->__clock_}; + } + + inline auto test_context::now() const noexcept -> test_context::time_point { + return __clock_.now(); + } + + namespace _tst_sched { + struct __recording_receiver { + using __t = __recording_receiver; + using __id = __recording_receiver; + using receiver_concept = stdexec::receiver_t; + + test_context* __context_; + stdexec::inplace_stop_source* __stop_source_; + + void set_value() noexcept { + __stop_source_->request_stop(); + __context_->request_stop(); + } + void set_error(std::exception_ptr) noexcept { + __stop_source_->request_stop(); + __context_->request_stop(); + } + void set_stopped() noexcept { + __stop_source_->request_stop(); + __context_->request_stop(); + } + + using env_t = decltype(stdexec::__env::__join( + stdexec::prop{stdexec::get_scheduler, stdexec::__declval()}, + stdexec::prop{stdexec::get_stop_token, stdexec::__declval()})); + [[nodiscard]] auto get_env() const noexcept -> env_t { + return stdexec::__env::__join( + stdexec::prop{stdexec::get_scheduler, __context_->get_scheduler()}, + stdexec::prop{stdexec::get_stop_token, __stop_source_->get_token()}); + } + }; + + template + struct __next_receiver { + using __t = __next_receiver; + using __id = __next_receiver; + using receiver_concept = stdexec::receiver_t; + + using _Receiver = stdexec::__t<_ReceiverId>; + + _Receiver* __receiver_; + + void set_value() noexcept { + } + template + void set_error(_Error&&) noexcept { + } + void set_stopped() noexcept { + } + + [[nodiscard]] auto get_env() const noexcept -> stdexec::env_of_t<_Receiver> { + return stdexec::get_env(*__receiver_); + } + }; + + // + // __proxy.. are a hammer to workaround a type handling bug in clang 19 + // + + template + struct __proxy_fn { + template + requires stdexec::__decays_to_derived_from<_Base, _Derived> + auto operator()(_Derived&& __derived, _Args&&... __args) const + noexcept( + stdexec::__nothrow_callable, _Args...>) + -> stdexec::__call_result_t, _Args...> { + return _Fn( + static_cast&&>(__derived) + , static_cast<_Args&&>(__args)...); + }; + }; + + template + struct __proxy_operation { + using _Sender = stdexec::__t<_SenderId>; + static_assert(stdexec::__callable); + using __operation_t = stdexec::connect_result_t<_Sender, _Receiver>; + using __t [[maybe_unused]] = __proxy_operation; + using __id [[maybe_unused]] = __proxy_operation; + + __operation_t __op_; + + __proxy_operation([[maybe_unused]] _Sender&& __sender, [[maybe_unused]] _Receiver&& __receiver) + noexcept(stdexec::__nothrow_connectable<_Sender, _Receiver>) + : __op_{ + stdexec::connect( + static_cast<_Sender&&>(__sender) + , static_cast<_Receiver&&>(__receiver)) + } + {} + + void start() noexcept { + stdexec::start(__op_); + } + }; + + template + struct __proxy_sender { + using _Sender = stdexec::__t<_SenderId>; + using __t [[maybe_unused]] = __proxy_sender; + using __id [[maybe_unused]] = __proxy_sender; + using sender_concept = typename _Sender::sender_concept; + + _Sender __sender_; + + explicit __proxy_sender(_Sender __sender) : __sender_{__sender} {} + + static constexpr auto get_completion_signatures = + [] _Self, class... _Env> + (_Self&&, _Env&&...) + noexcept(stdexec::__nothrow_callable< + stdexec::get_completion_signatures_t, + stdexec::__copy_cvref_t<_Self, _Sender> + , _Env...>) + -> stdexec::completion_signatures_of_t< + stdexec::__copy_cvref_t<_Self, _Sender> + , _Env...> { + return {}; + }; + + template + using __operation_t = __proxy_operation<_SenderId, _Receiver>; + + static constexpr auto connect = + [] _Self, class _Receiver> + (_Self&& __self, _Receiver&& __receiver) + noexcept(stdexec::__nothrow_connectable<_Sender, _Receiver>) + -> __operation_t<_Receiver> { + return __operation_t<_Receiver>{ + static_cast&&>(__self.__sender_) + , static_cast<_Receiver&&>(__receiver)}; + }; + }; + + // + // __test_sequence.. is a sequence-sender that produces a set of marbles as signals + // + + struct __test_sequence_operation_base { + test_context* __context_; + stdexec::inplace_stop_source __stop_source_{}; + + void request_stop() { + __stop_source_.request_stop(); + } + void stop_context() { + __context_->request_stop(); + } + }; + + template + struct __test_sequence_operation : __test_sequence_operation_base { + using _Receiver = stdexec::__t<_ReceiverId>; + + using __marble_t = marble_t; + using __marble_sender_t = __marble_t::__marble_sender_t; + using __time_point_t = typename test_scheduler::time_point; + + std::vector<__marble_t> __marbles_; + _Receiver __receiver_; + __marble_t* __end_marble_{nullptr}; + int __active_ops_ = 0; + + __marble_t __requested_stop_marble_{__time_point_t{}, sequence_stopped}; + + struct __stop_callback_fn_t { + __test_sequence_operation* __self_; + void operator()() const noexcept { + auto& __self = *__self_; + __self.__requested_stop_marble_.set_origin_frame(__self.__context_->now()); + // cancel all pending ops + __self_->request_stop(); + if (__self.__active_ops_ == 0) { + if (!__self.__end_marble_) { + __self.__requested_stop_marble_.visit_sequence_receiver(static_cast<_Receiver&&>(__self.__receiver_)); + } + } + } + }; + + using __stop_callback_t = stdexec::stop_callback_for_t< + stdexec::stop_token_of_t> + , __stop_callback_fn_t>; + + std::optional<__stop_callback_t> __on_stop_; + + template + static auto __schedule_at(__test_sequence_operation& __self, __marble_t& __marble, _Completion&& __completion) noexcept { + return stdexec::write_env( + // schedule the marble completion at the specified frame + exec::sequence( + exec::schedule_at(__self.__context_->get_scheduler(), __marble.frame()) + , static_cast<_Completion&&>(__completion)), + stdexec::prop{stdexec::get_stop_token, __self.__stop_source_.get_token()}) + | exec::finally( + stdexec::just() + | stdexec::then([&__self, &__marble]() noexcept { + // after each completion, update the __test_sequence_operation state + STDEXEC_ASSERT(__self.__active_ops_ > 0); + if ( + __marble.error_notification() + || __marble.stopped_notification() + || __marble.sequence_error() + || __marble.sequence_stopped() + || __marble.sequence_end()) { + // these marbles trigger the whole sequence + // to complete with no more items + if (!__self.__end_marble_) { + // set as the end marble + // this determines the signal that will be used to + // complete the sequence after all remaining active + // operations have completed + __self.__end_marble_ = &__marble; + } + // cancel all pending ops + __self.request_stop(); + } + if (--__self.__active_ops_ == 0) { + // all ops are complete, + if (!!__self.__end_marble_) { + __self.__on_stop_.reset(); + __self.__end_marble_->visit_sequence_receiver(static_cast<_Receiver&&>(__self.__receiver_)); + } + // else this sequence never completes - + // this sequence must be stopped externally + } + })); + } + using __receiver_t = __next_receiver<_ReceiverId>; + static auto __schedule_marble(__test_sequence_operation& __self, __marble_t& __marble) noexcept { + using __next_sender_t = decltype( + __schedule_at(__self, __marble, exec::set_next(__self.__receiver_, __marble.visit_sender()))); + using __end_sender_t = decltype( + __schedule_at(__self, __marble, stdexec::just())); + struct __next_sender_id { + using __t = __next_sender_t; + }; + struct __end_sender_id { + using __t = __end_sender_t; + }; + + // WORKAROUND clang 19 would fail to compile the construction of the variant_sender. + // It was unable to find the matching value in the variant that would be constructed. + // __proxy_sender is a hammer to force the types to look different enough to + // distinguish which variant value to construct + using __next_sender_proxy_t = __proxy_sender<__next_sender_id>; + using __end_sender_proxy_t = __proxy_sender<__end_sender_id>; + + using __result_t = variant_sender<__next_sender_proxy_t, __end_sender_proxy_t>; + if (__marble.__notification_.has_value()) { + return __result_t{__next_sender_proxy_t{{ + __schedule_at(__self, __marble, exec::set_next(__self.__receiver_, __marble.visit_sender()))}}}; + } else { + return __result_t{__end_sender_proxy_t{{ + __schedule_at(__self, __marble, stdexec::just())}}}; + } + } + + using __scheduled_marble_t = stdexec::__call_result_t< + decltype(&__schedule_marble) + , __test_sequence_operation& + , __marble_t&>; + using __marble_op_t = stdexec::connect_result_t<__scheduled_marble_t, __receiver_t>; + + std::deque> __marble_ops_{}; + + __test_sequence_operation( + std::vector<__marble_t> __marbles + , test_context* __context + , _Receiver&& __receiver) noexcept + : __test_sequence_operation_base{__context} + , __marbles_{static_cast&&>(__marbles)} + , __receiver_{static_cast<_Receiver&&>(__receiver)} { + } + + void start() noexcept { + __on_stop_.emplace( + stdexec::get_stop_token(stdexec::get_env(__receiver_)) + , __stop_callback_fn_t{this} + ); + for(auto& __marble : __marbles_) { + __marble.set_origin_frame(__context_->now()); + auto& __op = __marble_ops_.emplace_back(); + __op.__emplace_from([this, &__marble](){ + return stdexec::connect( + __schedule_marble(*this, __marble) + , __receiver_t{&__receiver_}); + }); + } + + __active_ops_ = __marble_ops_.size(); + for(auto& __op : __marble_ops_) { + stdexec::start(__op.value()); + } + } + }; + + struct __test_sequence { + using __t = __test_sequence; + using __id = __test_sequence; + using sender_concept = exec::sequence_sender_t; + + using marble_t = marble_t; + using marble_sender_t = marble_t::__marble_sender_t; + + test_context* __context_; + std::vector __marbles_; + + template _Self, class... _Env> + static auto get_item_types(_Self&&, _Env&&...) noexcept + -> item_types { + return {}; + } + + template _Self, class... _Env> + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept + -> stdexec::completion_signatures< + stdexec::set_value_t() + , stdexec::set_error_t(std::error_code) + , stdexec::set_error_t(std::exception_ptr) + , stdexec::set_stopped_t()>{ + return {}; + } + + static constexpr auto subscribe = + [] _Sequence, stdexec::receiver _Receiver> + (_Sequence&& __sequence, _Receiver __receiver) noexcept + -> __test_sequence_operation> { + return { + static_cast<_Sequence&&>(__sequence).__marbles_ + , static_cast<_Sequence&&>(__sequence).__context_ + , static_cast<_Receiver&&>(__receiver)}; + }; + }; + } // namespace _tst_sched + + template + inline auto test_context::get_marbles_from( + _Sequence&& __sequence + , typename test_clock::duration __stop_after) noexcept + -> std::vector> { + + std::vector> __recording; + stdexec::inplace_stop_source __source; + auto __clock = get_clock(); + + auto __op = stdexec::connect( + stdexec::when_all( + // record the sequence + exec::sequence( + // schedule connect and start of the sequence being recorded + // on the test scheduler + exec::schedule_at(get_scheduler(), __clock.now()) + , record_marbles( + &__recording + , __clock + , static_cast<_Sequence&&>(__sequence)) + // always complete with set_stopped to prevent the following + // scheduled request_stop from affecting the clock + , stdexec::just_stopped()) + // this is used to stop a 'never' sequence + , exec::schedule_at(get_scheduler(), __clock.now() + __stop_after) + | stdexec::then([&__source]() noexcept { __source.request_stop(); })) + , _tst_sched::__recording_receiver{this, &__source}); + stdexec::start(__op); + + // dispatch the test context queues + run(); + + return __recording; + } + + inline auto test_context::get_marble_sequence_from(std::vector> __marbles) noexcept + -> _tst_sched::__test_sequence { + return {this, static_cast>&&>(__marbles)}; + } + + template + inline auto test_context::get_marble_sequence_from(stdexec::__mstring<_Len> __diagram) noexcept + -> _tst_sched::__test_sequence { + return get_marble_sequence_from(get_marbles_from(__diagram)); + } +} // namespace exec diff --git a/test/exec/CMakeLists.txt b/test/exec/CMakeLists.txt index ca8ff54f0..a64e94a42 100644 --- a/test/exec/CMakeLists.txt +++ b/test/exec/CMakeLists.txt @@ -49,7 +49,9 @@ set(exec_test_sources sequence/test_empty_sequence.cpp sequence/test_ignore_all_values.cpp sequence/test_iterate.cpp + sequence/test_test_scheduler.cpp sequence/test_transform_each.cpp + sequence/test_marbles.cpp sequence/test_merge.cpp sequence/test_merge_each.cpp sequence/test_merge_each_threaded.cpp diff --git a/test/exec/sequence/test_marbles.cpp b/test/exec/sequence/test_marbles.cpp new file mode 100644 index 000000000..3b18c2f25 --- /dev/null +++ b/test/exec/sequence/test_marbles.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "exec/sequence/marbles.hpp" + +#include "exec/sequence/empty_sequence.hpp" +#include "exec/sequence/merge.hpp" +#include "stdexec/__detail/__meta.hpp" +#include + +#include +#include +#include +#include +#include + +namespace { + + struct __clock_t + { + using duration = std::chrono::milliseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point<__clock_t >; + [[maybe_unused]] static const bool is_steady = true; + + time_point __now_{}; + + [[maybe_unused]] time_point now() noexcept { + return __now_; + } + }; + + using __marble_t = exec::marble_t<__clock_t>; + using __marbles_t = std::vector<__marble_t>; + +# if STDEXEC_HAS_STD_RANGES() + + TEST_CASE( + "marbles - parse empty diagram", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto marbles = get_marbles_from(__clock, ""_mstr); + auto expected = __marbles_t{}; + CHECK(0 == marbles.size()); + CHECK(expected == marbles); + } + + TEST_CASE( + "marbles - parse never diagram", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto marbles = get_marbles_from(__clock, "--"_mstr); + auto expected = __marbles_t{}; + CHECK(0 == marbles.size()); + CHECK(expected == marbles); + } + + TEST_CASE( + "marbles - parse never with values diagram", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto marbles = get_marbles_from(__clock, "-a-b-"_mstr); + auto expected = __marbles_t{ + __marble_t{__clock.now() + 1ms, ex::set_value, 'a'}, + __marble_t{__clock.now() + 3ms, ex::set_value, 'b'} + }; + CHECK(2 == marbles.size()); + CHECK(expected == marbles); + } + + TEST_CASE( + "marbles - parse values diagram", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto marbles = get_marbles_from(__clock, "-a-b-|"_mstr); + auto expected = __marbles_t{ + __marble_t{__clock.now() + 1ms, ex::set_value, 'a'}, + __marble_t{__clock.now() + 3ms, ex::set_value, 'b'}, + __marble_t{__clock.now() + 5ms, sequence_end} + }; + CHECK(3 == marbles.size()); + CHECK(expected == marbles); + } + + TEST_CASE( + "marbles - parse values with skip ms diagram", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto marbles = get_marbles_from(__clock, "-a- 20ms b-|"_mstr); + auto expected = __marbles_t{ + __marble_t{__clock.now() + 1ms, ex::set_value, 'a'}, + __marble_t{__clock.now() + 23ms, ex::set_value, 'b'}, + __marble_t{__clock.now() + 25ms, sequence_end} + }; + CHECK(3 == marbles.size()); + CHECK(expected == marbles); + } + + TEST_CASE( + "marbles - parse values with skip s diagram", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto marbles = get_marbles_from(__clock, "-a- 2s b-|"_mstr); + auto expected = __marbles_t{ + __marble_t{__clock.now() + 1ms, ex::set_value, 'a'}, + __marble_t{__clock.now() + 2003ms, ex::set_value, 'b'}, + __marble_t{__clock.now() + 2005ms, sequence_end} + }; + CHECK(3 == marbles.size()); + CHECK(expected == marbles); + } + + TEST_CASE( + "marbles - parse values with skip m diagram", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto marbles = get_marbles_from(__clock, "-a- 2m b-|"_mstr); + auto expected = __marbles_t{ + __marble_t{__clock.now() + 1ms, ex::set_value, 'a'}, + __marble_t{__clock.now() + 120003ms, ex::set_value, 'b'}, + __marble_t{__clock.now() + 120005ms, sequence_end} + }; + CHECK(3 == marbles.size()); + CHECK(expected == marbles); + } + + TEST_CASE( + "marbles - parse values with skip first diagram", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto marbles = get_marbles_from(__clock, "20ms -a-b-|"_mstr); + auto expected = __marbles_t{ + __marble_t{__clock.now() + 21ms, ex::set_value, 'a'}, + __marble_t{__clock.now() + 23ms, ex::set_value, 'b'}, + __marble_t{__clock.now() + 25ms, sequence_end} + }; + CHECK(3 == marbles.size()); + CHECK(expected == marbles); + } + + TEST_CASE( + "marbles - record marbles of empty_sequence", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto actual = record_marbles(__clock, empty_sequence()); + auto expected = get_marbles_from(__clock, "=^|"_mstr); + CHECK(expected == actual); + } + + TEST_CASE( + "marbles - record marbles of range", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto actual = record_marbles(__clock, range('0', '3')); + auto expected = get_marbles_from(__clock, "=^(012|)"_mstr); + CHECK(expected == actual); + } + + TEST_CASE( + "marbles - record marbles of merged ranges", + "[sequence_senders][marbles]") { + __clock_t __clock{}; + auto actual = record_marbles(__clock, merge(range('0', '2'), range('2', '4'))); + auto expected = get_marbles_from(__clock, "=^(0123|)"_mstr); + CHECK(expected == actual); + } +# endif // STDEXEC_HAS_STD_RANGES() +} // namespace diff --git a/test/exec/sequence/test_merge_each.cpp b/test/exec/sequence/test_merge_each.cpp index 34a77a534..f15cd1164 100644 --- a/test/exec/sequence/test_merge_each.cpp +++ b/test/exec/sequence/test_merge_each.cpp @@ -20,27 +20,21 @@ #include "exec/sequence/merge.hpp" #include "exec/sequence/empty_sequence.hpp" #include "exec/sequence/iterate.hpp" +#include "exec/sequence/test_scheduler.hpp" #include "exec/sequence_senders.hpp" -#include "exec/variant_sender.hpp" -#include "exec/static_thread_pool.hpp" -#include "exec/timed_thread_scheduler.hpp" +#include "exec/timed_scheduler.hpp" #include "stdexec/__detail/__meta.hpp" -#include "stdexec/__detail/__read_env.hpp" +#include "stdexec/__detail/__senders_core.hpp" -#include #include #include #include +#include #include # include -# include -# include namespace { - using namespace std::chrono_literals; - using namespace exec; - namespace ex = stdexec; template concept __equivalent = __sequence_sndr::__all_contained_in<_A, _B> @@ -82,10 +76,6 @@ namespace { } }; - // a sequence adaptor that applies a function to each item - [[maybe_unused]] static constexpr auto then_each = [](auto f) { - return exec::transform_each(ex::then(f)); - }; // a sequence adaptor that schedules each item to complete // on the specified scheduler [[maybe_unused]] static constexpr auto continues_each_on = [](auto sched) { @@ -95,87 +85,13 @@ namespace { // on the specified scheduler after the specified duration [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { - return sequence(schedule_after(sched, after), stdexec::just(vs...)); + auto at = sched.now() + after; + return sequence(schedule_at(sched, at), stdexec::just(vs...)); })); }; - // a sequence adaptor that applies a function to each item - // the function must produce a sequence - // all the sequences returned from the function are merged - [[maybe_unused]] static constexpr auto flat_map = [](auto&& f) { - auto map_merge = [](auto&& sequence, auto&& f) noexcept { - return merge_each(exec::transform_each( - static_cast(sequence), - ex::then(static_cast(f)))); - }; - return stdexec::__binder_back{{static_cast(f)}, {}, {}}; - }; - // when_all requires a successful completion - // however stop_after_on has no successful completion - // this uses variant_sender to add a successful completion - // (the successful completion will never occur) - [[maybe_unused]] static constexpr auto with_void = [](auto&& sender) noexcept - -> variant_sender< - stdexec::__call_result_t, - decltype(sender)> { - return {static_cast(sender)}; - }; - // with_stop_token_from adds get_stop_token query, that returns the - // token for the provided stop_source, to the receiver env - [[maybe_unused]] static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { - return ex::write_env(ex::prop{ex::get_stop_token, stop_source.get_token()}); - }; - // log_start completes with the provided sequence after printing provided string - [[maybe_unused]] auto log_start = [](auto sequence, auto message) { - return exec::sequence( - ex::read_env(ex::get_stop_token) - | stdexec::then([message](auto&& token) noexcept { - UNSCOPED_INFO(message - << (token.stop_requested() ? ", stop was requested" : ", stop not requested") - << ", on thread id: " << std::this_thread::get_id()); - }), - ex::just(sequence)); - }; - // log_sequence prints the message when each value in the sequence is emitted - [[maybe_unused]] auto log_sequence = [](auto sequence, auto message) { - return sequence - | then_each([message](auto&& value) mutable noexcept { - UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); - return value; - }); - }; - // emits_stopped completes with set_stopped after printing info - [[maybe_unused]] auto emits_stopped = []() { - return ex::just() - | stdexec::let_value([]() noexcept { - UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); - return ex::just_stopped(); - }); - }; - // emits_error completes with set_error(error) after printing info - [[maybe_unused]] auto emits_error = [](auto error) { - return ex::just() - | stdexec::let_value([error]() noexcept { - UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); - }; #if STDEXEC_HAS_STD_RANGES() - // a sequence of numbers from itoa() - [[maybe_unused]] static constexpr auto range = [](auto from, auto to) { - return exec::iterate(std::views::iota(from, to)); - }; - - template - struct as_sequence_t : Sender { - using sender_concept = sequence_sender_t; - using item_types = exec::item_types; - auto subscribe(auto receiver) { - return connect(set_next(receiver, *static_cast(this)), receiver); - } - }; - TEST_CASE( "merge_each - merge two sequence senders of no elements", "[sequence_senders][merge_each][empty_sequence]") { @@ -338,11 +254,140 @@ namespace { CHECK(v.has_value() == true); } + + TEST_CASE( + "merge_each - merge_each of marble sequences", + "[sequence_senders][merge_each][merge]") { + + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence0 = __test.get_marble_sequence_from( + " 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from( + " -1-3 -4|"_mstr); + auto expected = get_marbles_from(__clock, + "=^01-(23)-4|"_mstr); + auto actual = __test.get_marbles_from(merge_each(merge(stdexec::just(__sequence0), stdexec::just(__sequence1)))); + CHECK(test_clock::time_point{6ms} == __clock.now()); + CAPTURE(__sequence0.__marbles_); + CAPTURE(__sequence1.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "merge_each - merge_each of marble sequences - concat", + "[sequence_senders][merge_each][iterate]") { + + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence0 = __test.get_marble_sequence_from( + " 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from( + " -1-3-4|"_mstr); + auto expected = get_marbles_from(__clock, + "=^0--2-1-3-4|"_mstr); + std::array<_tst_sched::__test_sequence, 2> __sequences{__sequence0, __sequence1}; + auto actual = __test.get_marbles_from(merge_each(iterate(__test.get_scheduler(), std::views::all(__sequences)))); + CHECK(test_clock::time_point{10ms} == __clock.now()); + CAPTURE(__sequence0.__marbles_); + CAPTURE(__sequence1.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "merge_each - merge_each of marble sequences with error", + "[sequence_senders][merge_each][merge]") { + + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence0 = __test.get_marble_sequence_from( + " 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from( + " -1-3#-4|"_mstr); + auto expected = get_marbles_from(__clock, + // TODO FIX set_stopped issued instead of set_error + "=^01-(23)#$"_mstr); + auto actual = __test.get_marbles_from(merge_each(merge(stdexec::just(__sequence0), stdexec::just(__sequence1)))); + CHECK(test_clock::time_point{4ms} == __clock.now()); + CAPTURE(__sequence0.__marbles_); + CAPTURE(__sequence1.__marbles_); + CHECK(expected == actual); + } + + + TEST_CASE( + "merge_each - merge_each of marble sequences with error - concat", + "[sequence_senders][merge_each][iterate]") { + + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence0 = __test.get_marble_sequence_from( + " 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from( + " -1-3#-4|"_mstr); + auto expected = get_marbles_from(__clock, + // TODO FIX set_stopped issued instead of set_error + "=^0--2-1-3#$"_mstr); + std::array<_tst_sched::__test_sequence, 2> __sequences{__sequence0, __sequence1}; + auto actual = __test.get_marbles_from(merge_each(iterate(__test.get_scheduler(), std::views::all(__sequences)))); + CHECK(test_clock::time_point{8ms} == __clock.now()); + CAPTURE(__sequence0.__marbles_); + CAPTURE(__sequence1.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "merge_each - merge_each of marble sequences with a value stopped", + "[sequence_senders][merge_each][merge]") { + + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence0 = __test.get_marble_sequence_from( + " 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from( + " -1-3.-4|"_mstr); + auto expected = get_marbles_from(__clock, + // TODO FIX set_stopped issued instead of set_error + "=^01-(23).$"_mstr); + auto actual = __test.get_marbles_from(merge_each(merge(stdexec::just(__sequence0), stdexec::just(__sequence1)))); + CHECK(test_clock::time_point{4ms} == __clock.now()); + CAPTURE(__sequence0.__marbles_); + CAPTURE(__sequence1.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "merge_each - merge_each of marble sequences with a value stopped - concat", + "[sequence_senders][merge_each][iterate]") { + + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence0 = __test.get_marble_sequence_from( + " 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from( + " -1-3.-4|"_mstr); + auto expected = get_marbles_from(__clock, + // TODO FIX set_stopped issued instead of set_error + "=^0--2-1-3.$"_mstr); + std::array<_tst_sched::__test_sequence, 2> __sequences{__sequence0, __sequence1}; + auto actual = __test.get_marbles_from(merge_each(iterate(__test.get_scheduler(), std::views::all(__sequences)))); + CHECK(test_clock::time_point{8ms} == __clock.now()); + CAPTURE(__sequence0.__marbles_); + CAPTURE(__sequence1.__marbles_); + CHECK(expected == actual); + } + // TODO - fix problem with stopping #if 0 TEST_CASE( "merge_each - merge_each sender stops when a nested sequence fails", - "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { + "[sequence_senders][merge_each][merge][iterate]") { auto sequences = merge( log_start(range(100, 120), "range 100-120"), diff --git a/test/exec/sequence/test_merge_each_threaded.cpp b/test/exec/sequence/test_merge_each_threaded.cpp index 61cab3470..1e8d3a2f5 100644 --- a/test/exec/sequence/test_merge_each_threaded.cpp +++ b/test/exec/sequence/test_merge_each_threaded.cpp @@ -243,7 +243,6 @@ namespace { // a sequence whose items are sequences auto sequences = merge( - ex::just(stop_after_on(sched1, 10ms)), // no items ex::just(range(100, 120)), // int items ex::just(empty_sequence()), // no items ex::just(range(200, 220)), // int items @@ -316,7 +315,6 @@ namespace { // a sequence whose items are sequences auto sequences = merge( - ex::just(stop_after_on(sched1, 10ms)), // no items ex::just(range(100, 120)), // int items ex::just(empty_sequence()), // no items ex::just(range(200, 220)), // int items diff --git a/test/exec/sequence/test_test_scheduler.cpp b/test/exec/sequence/test_test_scheduler.cpp new file mode 100644 index 000000000..33c175bf6 --- /dev/null +++ b/test/exec/sequence/test_test_scheduler.cpp @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "exec/sequence/test_scheduler.hpp" + +#include "exec/sequence/marbles.hpp" +#include "exec/sequence/merge.hpp" +#include "exec/sequence/transform_each.hpp" +#include "exec/sequence.hpp" +#include "stdexec/__detail/__just.hpp" +#include "stdexec/__detail/__meta.hpp" +#include + +#include +#include +#include +#include +#include + +namespace { + + // a sequence adaptor that schedules each item to complete + // on the specified scheduler + [[maybe_unused]] static constexpr auto continues_each_on = [](auto sched) { + return exec::transform_each(ex::continues_on(sched)); + }; + // a sequence adaptor that schedules each item to complete + // on the specified scheduler after the specified duration + [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { + return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { + auto at = sched.now() + after; + return sequence(schedule_at(sched, at), stdexec::just(vs...)); + })); + }; + + using __marble_t = exec::marble_t; + using __marbles_t = std::vector<__marble_t>; + +# if STDEXEC_HAS_STD_RANGES() + + TEST_CASE( + "test_scheduler - parse empty diagram", + "[sequence_senders][test_scheduler][marbles]") { + test_context __test{}; + auto __clock = __test.get_clock(); + auto marbles = get_marbles_from(__clock, ""_mstr); + auto expected = __marbles_t{}; + CHECK(marbles.size() == 0); + CHECK(marbles == expected); + } + + TEST_CASE( + "test_scheduler - record marbles via test_context", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(__clock.now() == test_clock::time_point{0ms}); + auto __scheduler = __test.get_scheduler(); + auto __sequence = __scheduler.schedule() | stdexec::then([]() noexcept { return '0'; }); + auto actual = __test.get_marbles_from(__sequence); + CHECK(__clock.now() == test_clock::time_point{0ms}); + auto expected = get_marbles_from(__clock, + "=^(0|)"_mstr); + CHECK(actual == expected); + } + + TEST_CASE( + "test_scheduler - test_context schedule_after advances test_clock", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(__clock.now() == test_clock::time_point{0ms}); + auto __scheduler = __test.get_scheduler(); + auto __sequence = schedule_after(__scheduler, 2ms) | stdexec::then([]() noexcept { return '0'; }); + auto expected = get_marbles_from(__clock, + "=^--(0|)"_mstr); + auto actual = __test.get_marbles_from(__sequence); + CHECK(test_clock::time_point{2ms} == __clock.now()); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context marble-sequence advances test_clock", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence = __test.get_marble_sequence_from( + " -a--b---c|"_mstr); + auto expected = get_marbles_from(__clock, + "=^-a--b---c|"_mstr); + auto actual = __test.get_marbles_from(__sequence); + CHECK(test_clock::time_point{9ms} == __clock.now()); + CAPTURE(__sequence.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context marble-sequence never", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence = __test.get_marble_sequence_from( + " -0-"_mstr); + auto expected = get_marbles_from(__clock, + "=^-5 998ms $"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + CHECK(test_clock::time_point{1000ms} == __clock.now()); + CAPTURE(__sequence.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context marble-sequence error", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence = __test.get_marble_sequence_from( + " -0--#"_mstr); + auto expected = get_marbles_from(__clock, + "=^-5--#$"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + CHECK(test_clock::time_point{4ms} == __clock.now()); + CAPTURE(__sequence.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context marble-sequence error in middle", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence = __test.get_marble_sequence_from( + " -0--#--1|"_mstr); + auto expected = get_marbles_from(__clock, + "=^-5--#$"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + CHECK(test_clock::time_point{4ms} == __clock.now()); + CAPTURE(__sequence.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context marble-sequence stopped", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence = __test.get_marble_sequence_from( + " -0--."_mstr); + auto expected = get_marbles_from(__clock, + "=^-5--.$"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + CHECK(test_clock::time_point{4ms} == __clock.now()); + CAPTURE(__sequence.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context marble-sequence stopped in middle", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence = __test.get_marble_sequence_from( + " -0--.--1|"_mstr); + auto expected = get_marbles_from(__clock, + "=^-5--.$"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + CHECK(test_clock::time_point{4ms} == __clock.now()); + CAPTURE(__sequence.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context marble-sequence transform", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence = __test.get_marble_sequence_from( + " -0--1---2|"_mstr); + auto expected = get_marbles_from(__clock, + "=^-5--6---7|"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + CHECK(test_clock::time_point{9ms} == __clock.now()); + CAPTURE(__sequence.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context marble-sequence simple shift", + "[sequence_senders][test_scheduler]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence = __test.get_marble_sequence_from( + " 012--|"_mstr); + auto expected = get_marbles_from(__clock, + "=^--012|"_mstr); + auto actual = __test.get_marbles_from(__sequence | delays_each_on(__test.get_scheduler(), 2ms)); + CHECK(test_clock::time_point{5ms} == __clock.now()); + CAPTURE(__sequence.__marbles_); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context multi-second marble-sequence shift", + "[sequence_senders][test_scheduler]") { + auto __real_time_now = std::chrono::steady_clock::now(); + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + + auto __sequence = __test.get_marble_sequence_from( + " 5s 0 5s 1 5s 2 100ms |"_mstr); + auto expected = get_marbles_from(__clock, + "=^ 5s 100ms 0 5s 1 5s 2 |"_mstr); + + auto actual = __test.get_marbles_from(__sequence | delays_each_on(__test.get_scheduler(), 100ms), 16s); + + CHECK(test_clock::time_point{5s + 5s + 5s + 100ms + 3ms} == __clock.now()); + + auto __real_time_elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - __real_time_now); + CAPTURE(__sequence.__marbles_); + CAPTURE(__real_time_elapsed); + CHECK(expected == actual); + } + + TEST_CASE( + "test_scheduler - test_context marble-sequence merge", + "[sequence_senders][test_scheduler][merge]") { + test_context __test{}; + auto __clock = __test.get_clock(); + CHECK(test_clock::time_point{0ms} == __clock.now()); + auto __sequence0 = __test.get_marble_sequence_from( + " 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from( + " -1-3 -4|"_mstr); + auto expected = get_marbles_from(__clock, + "=^01-(23)-4|"_mstr); + auto actual = __test.get_marbles_from(merge(__sequence0, __sequence1)); + CHECK(test_clock::time_point{6ms} == __clock.now()); + CAPTURE(__sequence0.__marbles_); + CAPTURE(__sequence1.__marbles_); + CHECK(expected == actual); + } + +# endif // STDEXEC_HAS_STD_RANGES() +} // namespace diff --git a/test/test_common/sequences.hpp b/test/test_common/sequences.hpp new file mode 100644 index 000000000..9a7e975bf --- /dev/null +++ b/test/test_common/sequences.hpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2023 Maikel Nadolski + * Copyright (c) 2023 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "exec/sequence/iterate.hpp" +#include "exec/sequence/transform_each.hpp" +#include "exec/variant_sender.hpp" +#include "stdexec/__detail/__then.hpp" + +#include + +namespace std { + inline std::string to_string(const std::error_code __error) noexcept { + return __error.message(); + } + inline std::string to_string(const std::exception_ptr __ex) noexcept { + try { + std::rethrow_exception(__ex); + } catch(const std::exception& __ex) { + return __ex.what(); + } + } +} // namespace std + +namespace stdexec::__rcvrs { + inline std::string to_string(set_value_t) noexcept { + return {"set_value"}; + } + inline std::string to_string(set_error_t) noexcept { + return {"set_error"}; + } + inline std::string to_string(set_stopped_t) noexcept { + return {"set_stopped"}; + } +} // namespace stdexec::__rcvrs + +namespace exec::__sequence_sender { + inline std::string to_string(set_next_t) noexcept { + return {"set_next"}; + } +} // namespace exec::__sequence_sender + +namespace exec::__marbles { + inline std::string to_string(sequence_start_t) noexcept { + return {"sequence_start"}; + } + inline std::string to_string(sequence_connect_t) noexcept { + return {"sequence_connect"}; + } + inline std::string to_string(sequence_end_t) noexcept { + return {"sequence_end"}; + } + inline std::string to_string(sequence_error_t) noexcept { + return {"sequence_error"}; + } + inline std::string to_string(sequence_stopped_t) noexcept { + return {"sequence_stopped"}; + } + inline std::string to_string(request_stop_t) noexcept { + return {"request_stop"}; + } +} // namespace exec::__marbles + +namespace Catch { + template + struct StringMaker> { + static std::string convert(const std::chrono::time_point<_Clock, _Duration>& __at) { + return std::to_string(std::chrono::duration_cast(__at.time_since_epoch()).count()) + "ms"; + } + }; + + template + struct StringMaker> { + static std::string convert(const std::chrono::duration<_Rep, _Period>& __duration) { + return std::to_string(std::chrono::duration_cast(__duration).count()) + "ms"; + } + }; + + template + struct StringMaker> { + static std::string convert(const exec::marble_t<_Clock>& __value) { + return to_string(__value); + } + }; + + template + struct StringMaker> { + static std::string convert(const exec::notification_t<_CompletionSignatures>& __value) { + return to_string(__value); + } + }; + + template _Tag> + struct StringMaker<_Tag> { + static std::string convert(const _Tag& __tag) { + return to_string(__tag); + } + }; +} // namespace Catch + +namespace { + using namespace exec; + namespace ex = stdexec; + using ex::operator""_mstr; + using namespace std::chrono_literals; + + template + struct as_sequence_t : Sender { + using sender_concept = sequence_sender_t; + template _Self, class... _Env> + static auto get_item_types(_Self&&, _Env&&...) noexcept -> exec::__item_types_of_t { + return {}; + } + auto subscribe(auto receiver) { + return connect(set_next(receiver, *static_cast(this)), receiver); + } + }; + + // a sequence adaptor that applies a function to each item + [[maybe_unused]] static constexpr auto then_each = [](auto f) { + return exec::transform_each(stdexec::then(f)); + }; + // a sequence adaptor that applies a function to each item + // the function must produce a sequence + // all the sequences returned from the function are merged + [[maybe_unused]] static constexpr auto flat_map = [](auto&& f) { + auto map_merge = [](auto&& sequence, auto&& f) noexcept { + return merge_each(exec::transform_each( + static_cast(sequence), + ex::then(static_cast(f)))); + }; + return stdexec::__binder_back{{static_cast(f)}, {}, {}}; + }; + // when_all requires a successful completion + // however stop_after_on has no successful completion + // this uses variant_sender to add a successful completion + // (the successful completion will never occur) + [[maybe_unused]] static constexpr auto with_void = [](auto&& sender) noexcept + -> variant_sender< + stdexec::__call_result_t, + decltype(sender)> { + return {static_cast(sender)}; + }; + // with_stop_token_from adds get_stop_token query, that returns the + // token for the provided stop_source, to the receiver env + [[maybe_unused]] static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { + return ex::write_env(ex::prop{ex::get_stop_token, stop_source.get_token()}); + }; + // log_start completes with the provided sequence after printing provided string + [[maybe_unused]] static constexpr auto log_start = [](auto sequence, auto message) { + return exec::sequence( + ex::read_env(ex::get_stop_token) + | stdexec::then([message](auto&& token) noexcept { + UNSCOPED_INFO(message + << (token.stop_requested() ? ", stop was requested" : ", stop not requested") + << ", on thread id: " << std::this_thread::get_id()); + }), + ex::just(sequence)); + }; + // log_sequence prints the message when each value in the sequence is emitted + [[maybe_unused]] static constexpr auto log_sequence = [](auto sequence, auto message) { + return sequence + | then_each([message](auto&& value) mutable noexcept { + UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); + return value; + }); + }; + // emits_stopped completes with set_stopped after printing info + [[maybe_unused]] static constexpr auto emits_stopped = []() { + return ex::just() + | stdexec::let_value([]() noexcept { + UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); + }; + // emits_error completes with set_error(error) after printing info + [[maybe_unused]] static constexpr auto emits_error = [](auto error) { + return ex::just() + | stdexec::let_value([error]() noexcept { + UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); + }; + +# if STDEXEC_HAS_STD_RANGES() + + // a sequence of numbers from itoa() + [[maybe_unused]] static constexpr auto range = [](auto from, auto to) { + return exec::iterate(std::views::iota(from, to)); + }; + +# endif // STDEXEC_HAS_STD_RANGES() + +} // namespace From da25787294fc92086a50ec18c363e84d42e897d5 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Tue, 21 Oct 2025 15:56:37 +0000 Subject: [PATCH 19/39] remove windows-only compilation error debug aid that is breaking windows builds that build cleanly. --- include/exec/sequence_senders.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 48e58c6ba..9c3892710 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -695,11 +695,6 @@ namespace exec { } else { stdexec::__diagnose_sender_concept_failure<_Sequence, _Env...>(); } -#if STDEXEC_MSVC() || STDEXEC_NVHPC() - // MSVC and NVHPC need more encouragement to print the type of the - // error. - _Completions __what = 0; -#endif } } From 54e46e22cea82c03d46d535f195257639145816e Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Wed, 29 Oct 2025 07:13:48 -0700 Subject: [PATCH 20/39] replace static_thread_pool with single_thread_context. static_thread_pool causes TSAN warnings. static_thread_pool examples also cause TSAN warnings --- test/exec/sequence/test_merge.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/exec/sequence/test_merge.cpp b/test/exec/sequence/test_merge.cpp index 2c832fd1b..89e8fd532 100644 --- a/test/exec/sequence/test_merge.cpp +++ b/test/exec/sequence/test_merge.cpp @@ -24,7 +24,7 @@ #include "exec/sequence.hpp" #include "exec/sequence_senders.hpp" #include "exec/trampoline_scheduler.hpp" -#include "exec/static_thread_pool.hpp" +#include "exec/single_thread_context.hpp" #include "stdexec/__detail/__just.hpp" #include "stdexec/__detail/__meta.hpp" #include "stdexec/__detail/__continues_on.hpp" @@ -169,13 +169,13 @@ struct null_receiver { "merge - merge sender merges all items from multiple threads", "[sequence_senders][static_thread_pool][merge][iterate]") { - exec::static_thread_pool ctx0{1}; + exec::single_thread_context ctx0; ex::scheduler auto sched0 = ctx0.get_scheduler(); - exec::static_thread_pool ctx1{1}; + exec::single_thread_context ctx1; ex::scheduler auto sched1 = ctx1.get_scheduler(); - exec::static_thread_pool ctx2{1}; + exec::single_thread_context ctx2; ex::scheduler auto sched2 = ctx2.get_scheduler(); - exec::static_thread_pool ctx3{1}; + exec::single_thread_context ctx3; ex::scheduler auto sched3 = ctx3.get_scheduler(); auto range = [](auto from, auto to) { From 8364f1ac75d6e1294c74742e7cdb382b645f2c20 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Wed, 29 Oct 2025 20:28:18 +0000 Subject: [PATCH 21/39] clang-format changes --- include/exec/__detail/__basic_sequence.hpp | 15 +- include/exec/sequence/ignore_all_values.hpp | 3 +- include/exec/sequence/merge.hpp | 83 +++---- include/exec/sequence/transform_each.hpp | 81 ++++--- include/exec/sequence_senders.hpp | 256 +++++++++++--------- test/exec/sequence/test_merge.cpp | 138 ++++++----- 6 files changed, 301 insertions(+), 275 deletions(-) diff --git a/include/exec/__detail/__basic_sequence.hpp b/include/exec/__detail/__basic_sequence.hpp index ff8cd4a7d..f50fbc134 100644 --- a/include/exec/__detail/__basic_sequence.hpp +++ b/include/exec/__detail/__basic_sequence.hpp @@ -73,9 +73,8 @@ namespace exec { template _Self, class... _Env> static auto get_item_types(_Self&& __self, _Env&&... __env) - -> decltype(__self.__tag().get_item_types( - static_cast<_Self&&>(__self), - static_cast<_Env&&>(__env)...)) { + -> decltype(__self.__tag() + .get_item_types(static_cast<_Self&&>(__self), static_cast<_Env&&>(__env)...)) { return {}; } @@ -88,11 +87,11 @@ namespace exec { } template - static auto - apply(_Sequence&& __sequence, _ApplyFn&& __fun) noexcept(stdexec::__nothrow_callable< - stdexec::__detail::__impl_of<_Sequence>, - stdexec::__copy_cvref_fn<_Sequence>, - _ApplyFn + static auto apply(_Sequence&& __sequence, _ApplyFn&& __fun) + noexcept(stdexec::__nothrow_callable< + stdexec::__detail::__impl_of<_Sequence>, + stdexec::__copy_cvref_fn<_Sequence>, + _ApplyFn >) -> stdexec::__call_result_t< stdexec::__detail::__impl_of<_Sequence>, diff --git a/include/exec/sequence/ignore_all_values.hpp b/include/exec/sequence/ignore_all_values.hpp index 7c403b692..08e49b1ea 100644 --- a/include/exec/sequence/ignore_all_values.hpp +++ b/include/exec/sequence/ignore_all_values.hpp @@ -317,8 +317,7 @@ namespace exec { static constexpr auto connect = [](_Sender&& __sndr, _Receiver __rcvr) noexcept( __nothrow_callable<__sexpr_apply_t, _Sender, __connect_fn<_Receiver>>) - -> __call_result_t<__sexpr_apply_t, _Sender, __connect_fn<_Receiver>> - { + -> __call_result_t<__sexpr_apply_t, _Sender, __connect_fn<_Receiver>> { static_assert(sender_expr_for<_Sender, ignore_all_values_t>); return __sexpr_apply(static_cast<_Sender&&>(__sndr), __connect_fn<_Receiver>{__rcvr}); }; diff --git a/include/exec/sequence/merge.hpp b/include/exec/sequence/merge.hpp index 01511e6ef..d028d7948 100644 --- a/include/exec/sequence/merge.hpp +++ b/include/exec/sequence/merge.hpp @@ -57,8 +57,7 @@ namespace exec { static_cast<_Receiver&&>(__op_->__receiver_), static_cast<_Error&&>(__error)); } - void set_stopped() noexcept - { + void set_stopped() noexcept { stdexec::set_stopped(static_cast<_Receiver&&>(__op_->__receiver_)); } @@ -73,26 +72,28 @@ namespace exec { using _Receiver = stdexec::__t<_ReceiverId>; template - auto operator()(_Item&& __item, __operation_base<_Receiver>* __op) const noexcept( - __nothrow_callable) - -> next_sender_of_t<_Receiver, _Item> { - return exec::set_next( - __op->__receiver_, static_cast<_Item&&>(__item)); + auto operator()(_Item&& __item, __operation_base<_Receiver>* __op) const + noexcept(__nothrow_callable) + -> next_sender_of_t<_Receiver, _Item> { + return exec::set_next(__op->__receiver_, static_cast<_Item&&>(__item)); } }; struct __combine { - template - using merge_each_fn_t = __binder_back<__merge_each_fn<_ReceiverId>, __operation_base<__t<_ReceiverId>>*>; - - template - using transform_sender_t = __call_result_t>; - template - using ignored_sender_t = __call_result_t>; - - template - using result_sender_t = __call_result_t...>; + template + using merge_each_fn_t = + __binder_back<__merge_each_fn<_ReceiverId>, __operation_base<__t<_ReceiverId>>*>; + + template + using transform_sender_t = + __call_result_t>; + template + using ignored_sender_t = + __call_result_t>; + + template + using result_sender_t = + __call_result_t...>; }; template @@ -101,23 +102,25 @@ namespace exec { using merge_each_fn_t = typename __combine::merge_each_fn_t<_ReceiverId>; - template - using result_sender_t = typename __combine::result_sender_t<_ReceiverIdDependent, _Sequences...>; + template + using result_sender_t = + typename __combine::result_sender_t<_ReceiverIdDependent, _Sequences...>; struct __t : __operation_base<_Receiver> { using __id = __operation; - connect_result_t, stdexec::__t<__result_receiver<_ReceiverId>>> __op_result_; + connect_result_t, stdexec::__t<__result_receiver<_ReceiverId>>> + __op_result_; __t(_Receiver __rcvr, _Sequences... __sequences) - : __operation_base< - _Receiver - >{static_cast<_Receiver&&>(__rcvr)} + : __operation_base<_Receiver>{static_cast<_Receiver&&>(__rcvr)} , __op_result_{stdexec::connect( - stdexec::when_all( - exec::ignore_all_values( - exec::transform_each(static_cast<_Sequences&&>(__sequences), merge_each_fn_t{{this}, {}, {}}))...), - stdexec::__t<__result_receiver<_ReceiverId>>{this})} { + stdexec::when_all( + exec::ignore_all_values( + exec::transform_each( + static_cast<_Sequences&&>(__sequences), + merge_each_fn_t{{this}, {}, {}}))...), + stdexec::__t<__result_receiver<_ReceiverId>>{this})} { } void start() & noexcept { @@ -132,24 +135,20 @@ namespace exec { template auto operator()(__ignore, __ignore, _Sequences... __sequences) noexcept( - (__nothrow_decay_copyable<_Sequences> && ...) - && __nothrow_move_constructible<_Receiver>) + (__nothrow_decay_copyable<_Sequences> && ...) && __nothrow_move_constructible<_Receiver>) -> __t<__operation<__id<_Receiver>, _Sequences...>> { - return { - static_cast<_Receiver&&>(__rcvr_), - static_cast<_Sequences&&>(__sequences)...}; + return {static_cast<_Receiver&&>(__rcvr_), static_cast<_Sequences&&>(__sequences)...}; } }; struct merge_t { template auto operator()(_Sequences&&... __sequences) const - noexcept((__nothrow_decay_copyable<_Sequences> && ...)) - -> __well_formed_sequence_sender auto { + noexcept((__nothrow_decay_copyable<_Sequences> && ...)) -> __well_formed_sequence_sender + auto { auto __domain = __common_domain_t<_Sequences...>(); return transform_sender( - __domain, make_sequence_expr(__(), - static_cast<_Sequences&&>(__sequences)...)); + __domain, make_sequence_expr(__(), static_cast<_Sequences&&>(__sequences)...)); } template @@ -180,7 +179,7 @@ namespace exec { template _Self, class... _Env> static auto get_completion_signatures(_Self&&, _Env&&...) noexcept { - return __minvoke<__mtry_catch<__q<__completions_t>, __q<__error_t>>, _Self, _Env...>(); + return __minvoke<__mtry_catch<__q<__completions_t>, __q<__error_t>>, _Self, _Env...>(); } template @@ -188,10 +187,12 @@ namespace exec { template using __f = stdexec::__mapply< - stdexec::__munique>, + stdexec::__munique>, stdexec::__minvoke< stdexec::__mconcat>, - __item_types_of_t<_Sequences, _Env...>...>>; + __item_types_of_t<_Sequences, _Env...>... + > + >; }; template @@ -199,7 +200,7 @@ namespace exec { template _Self, class... _Env> static auto get_item_types(_Self&&, _Env&&...) noexcept { - return __minvoke<__mtry_catch<__q<__items_t>, __q<__error_t>>, _Self, _Env...>(); + return __minvoke<__mtry_catch<__q<__items_t>, __q<__error_t>>, _Self, _Env...>(); } template _Self, receiver _Receiver> diff --git a/include/exec/sequence/transform_each.hpp b/include/exec/sequence/transform_each.hpp index cf8d0d743..3c8ad212b 100644 --- a/include/exec/sequence/transform_each.hpp +++ b/include/exec/sequence/transform_each.hpp @@ -135,11 +135,14 @@ namespace exec { template auto operator()(item_types<_Items...>*) -> decltype(( - stdexec::__msuccess(), ..., __try_adaptor_for_item(static_cast<_Items*>(nullptr)))); + stdexec::__msuccess(), + ..., + __try_adaptor_for_item(static_cast<_Items*>(nullptr)))); }; template - using __try_adaptor_calls_result_t = __call_result_t<__try_adaptor_calls_t>, _Items>; + using __try_adaptor_calls_result_t = + __call_result_t<__try_adaptor_calls_t>, _Items>; template concept __callable_adaptor_for = requires(_Items* __items) { @@ -150,7 +153,7 @@ namespace exec { template auto operator()(_Sequence&& __sndr, _Adaptor&& __adaptor) const noexcept(__nothrow_decay_copyable<_Sequence> && __nothrow_decay_copyable<_Adaptor>) - -> __well_formed_sequence_sender auto { + -> __well_formed_sequence_sender auto { return make_sequence_expr( static_cast<_Adaptor&&>(__adaptor), static_cast<_Sequence&&>(__sndr)); } @@ -166,8 +169,8 @@ namespace exec { using __completion_sigs_t = __sequence_completion_signatures_of_t<__child_of<_Self>, _Env...>; template _Self, class... _Env> - static auto - get_completion_signatures(_Self&&, _Env&&...) noexcept -> __completion_sigs_t<_Self, _Env...> { + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept + -> __completion_sigs_t<_Self, _Env...> { return {}; } @@ -180,57 +183,61 @@ namespace exec { __item_types_of_t<__child_of<_Self>, _Env...> >; - template - struct _TRANSFORM_EACH_ADAPTOR_INVOCATION_FAILED_ {}; + template + struct _TRANSFORM_EACH_ADAPTOR_INVOCATION_FAILED_ { }; - template _Self, class... _Env> - requires (!__mvalid<__item_types_t, _Self, _Env...>) - && __mvalid<__item_types_of_t, __child_of<_Self>, _Env...> - && (!__callable_adaptor_for< - __data_of<_Self>, - __item_types_of_t<__child_of<_Self>, _Env...> - >) - static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< - _TRANSFORM_EACH_ADAPTOR_INVOCATION_FAILED_<_Self>, - _WITH_SEQUENCE_<__child_of<_Self>>, - _WITH_ENVIRONMENT_<_Env...>, - _WITH_TYPE_<__try_adaptor_calls_result_t< - __data_of<_Self>, - __item_types_of_t<__child_of<_Self>, _Env...>>>>; - - template - struct _TRANSFORM_EACH_ITEM_TYPES_OF_THE_CHILD_ARE_INVALID_ {}; + template _Self, class... _Env> + requires(!__mvalid<__item_types_t, _Self, _Env...>) + && __mvalid<__item_types_of_t, __child_of<_Self>, _Env...> + && (!__callable_adaptor_for< + __data_of<_Self>, + __item_types_of_t<__child_of<_Self>, _Env...> + >) + static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< + _TRANSFORM_EACH_ADAPTOR_INVOCATION_FAILED_<_Self>, + _WITH_SEQUENCE_<__child_of<_Self>>, + _WITH_ENVIRONMENT_<_Env...>, + _WITH_TYPE_<__try_adaptor_calls_result_t< + __data_of<_Self>, + __item_types_of_t<__child_of<_Self>, _Env...> + >> + >; + + template + struct _TRANSFORM_EACH_ITEM_TYPES_OF_THE_CHILD_ARE_INVALID_ { }; template _Self, class... _Env> - requires (!__mvalid<__item_types_t, _Self, _Env...>) - && (!__mvalid<__item_types_of_t, __child_of<_Self>, _Env...>) + requires(!__mvalid<__item_types_t, _Self, _Env...>) + && (!__mvalid<__item_types_of_t, __child_of<_Self>, _Env...>) static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< _TRANSFORM_EACH_ITEM_TYPES_OF_THE_CHILD_ARE_INVALID_<_Self>, _WITH_SEQUENCE_<__child_of<_Self>>, - _WITH_ENVIRONMENT_<_Env...>>; + _WITH_ENVIRONMENT_<_Env...> + >; - template - struct _TRANSFORM_EACH_ITEM_TYPES_CALCULATION_FAILED_ {}; + template + struct _TRANSFORM_EACH_ITEM_TYPES_CALCULATION_FAILED_ { }; template _Self, class... _Env> - requires (!__mvalid<__item_types_t, _Self, _Env...>) - && __mvalid<__item_types_of_t, __child_of<_Self>, _Env...> - && __callable_adaptor_for< + requires(!__mvalid<__item_types_t, _Self, _Env...>) + && __mvalid<__item_types_of_t, __child_of<_Self>, _Env...> + && __callable_adaptor_for< __data_of<_Self>, __item_types_of_t<__child_of<_Self>, _Env...> - > + > static auto get_item_types(_Self&&, _Env&&...) noexcept -> __mexception< _TRANSFORM_EACH_ITEM_TYPES_CALCULATION_FAILED_<_Self>, _WITH_SEQUENCE_<__child_of<_Self>>, - _WITH_ENVIRONMENT_<_Env...>>; + _WITH_ENVIRONMENT_<_Env...> + >; template _Self, class... _Env> requires __mvalid<__item_types_t, _Self, _Env...> && __mvalid<__item_types_of_t, __child_of<_Self>, _Env...> && __callable_adaptor_for< - __data_of<_Self>, - __item_types_of_t<__child_of<_Self>, _Env...> - > + __data_of<_Self>, + __item_types_of_t<__child_of<_Self>, _Env...> + > static auto get_item_types(_Self&&, _Env&&...) noexcept -> __item_types_t<_Self, _Env...> { return {}; } diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 9c3892710..10cbf81ca 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -40,19 +40,20 @@ namespace exec { concept __all_contained_in_t = __v<__mall_contained_in_t<_Needles, _Haystack>>; } // namespace __sequence_sndr - // This concept checks if a given sender satisfies the requirements to be returned from `set_next`. + // This concept checks if a given sender satisfies the requirements to be returned from `set_next`. template > - concept next_sender = stdexec::sender_in<_Sender, _Env> - && __sequence_sndr::__all_contained_in_t< - stdexec::completion_signatures_of_t<_Sender, _Env>, - stdexec::completion_signatures - >; + concept next_sender = + stdexec::sender_in<_Sender, _Env> + && __sequence_sndr::__all_contained_in_t< + stdexec::completion_signatures_of_t<_Sender, _Env>, + stdexec::completion_signatures + >; namespace __sequence_sndr { template concept __has_set_next_member = requires(_Receiver& __rcvr, _Item&& __item) { - __rcvr.set_next(static_cast<_Item &&>(__item)); + __rcvr.set_next(static_cast<_Item&&>(__item)); }; // This is a sequence-receiver CPO that is used to apply algorithms on an input sender and it @@ -177,16 +178,17 @@ namespace exec { struct get_item_types_t; template - using __item_types_of_t = - __call_result_t; + using __item_types_of_t = __call_result_t; template - using __unrecognized_sequence_error_t = - __mexception<_UNRECOGNIZED_SEQUENCE_TYPE_<>, _WITH_SEQUENCE_<_Sequence>, _WITH_ENVIRONMENT_<_Env>...>; + using __unrecognized_sequence_error_t = __mexception< + _UNRECOGNIZED_SEQUENCE_TYPE_<>, + _WITH_SEQUENCE_<_Sequence>, + _WITH_ENVIRONMENT_<_Env>... + >; template - using __member_result_t = decltype(__declval<_Sequence>() - .get_item_types(__declval<_Env>())); + using __member_result_t = decltype(__declval<_Sequence>().get_item_types(__declval<_Env>())); template using __static_member_result_t = decltype(STDEXEC_REMOVE_REFERENCE( @@ -197,7 +199,8 @@ namespace exec { transform_sender_result_t<__late_domain_of_t<_Sequence, _Env>, _Sequence, _Env>; template - concept __with_tag_invoke = tag_invocable, _Env>; + concept __with_tag_invoke = + tag_invocable, _Env>; template using __member_alias_t = __decay_t<__tfx_sequence_t<_Sequence, _Env>>::item_types; @@ -227,13 +230,15 @@ namespace exec { using __result_t = __static_member_result_t<__tfx_sequence_t, _Env>; return static_cast<__result_t (*)()>(nullptr); } else if constexpr (__with_member<__tfx_sequence_t, _Env>) { - using __result_t = decltype(__declval<__tfx_sequence_t>().get_item_types(__declval<_Env>())); + using __result_t = decltype(__declval<__tfx_sequence_t>() + .get_item_types(__declval<_Env>())); return static_cast<__result_t (*)()>(nullptr); } else if constexpr (__with_tag_invoke<__tfx_sequence_t, _Env>) { using __result_t = tag_invoke_result_t; return static_cast<__result_t (*)()>(nullptr); } else if constexpr ( - sender_in<__tfx_sequence_t, _Env> && !enable_sequence_sender>) { + sender_in<__tfx_sequence_t, _Env> + && !enable_sequence_sender>) { using __result_t = item_types>; return static_cast<__result_t (*)()>(nullptr); } else if constexpr (__is_debug_env<_Env>) { @@ -249,8 +254,8 @@ namespace exec { } template > - constexpr auto - operator()(_Sequence&&, _Env&& = {}) const noexcept -> decltype(__impl<_Sequence, _Env>()()) { + constexpr auto operator()(_Sequence&&, _Env&& = {}) const noexcept + -> decltype(__impl<_Sequence, _Env>()()) { return {}; } }; @@ -259,13 +264,13 @@ namespace exec { using __sequence_sndr::get_item_types_t; inline constexpr get_item_types_t get_item_types{}; - template + template concept sequence_sender = stdexec::sender_in<_Sequence, _Env...> && enable_sequence_sender>; template concept has_sequence_item_types = requires(_Sequence&& __sequence, _Env&&... __env) { - { get_item_types(static_cast<_Sequence &&>(__sequence), static_cast<_Env &&>(__env)...) }; + { get_item_types(static_cast<_Sequence&&>(__sequence), static_cast<_Env&&>(__env)...) }; }; template @@ -298,7 +303,7 @@ namespace exec { struct _SEQUENCE_GET_ITEM_TYPES_RESULT_IS_NOT_WELL_FORMED_ { }; template - requires (!stdexec::__merror<_Items>) + requires(!stdexec::__merror<_Items>) auto __check_items(_Items*) -> stdexec::__mexception< _SEQUENCE_GET_ITEM_TYPES_RESULT_IS_NOT_WELL_FORMED_<_Items>, _WITH_SEQUENCE_<_Sequence> @@ -314,32 +319,30 @@ namespace exec { requires stdexec::__merror<_Sequence> auto __check_sequence(_Sequence*) -> _Sequence; - struct _SEQUENCE_GET_ITEM_TYPES_IS_NOT_WELL_FORMED_ {}; + struct _SEQUENCE_GET_ITEM_TYPES_IS_NOT_WELL_FORMED_ { }; template - requires (!stdexec::__merror<_Sequence>) - && (!stdexec::__mvalid<__item_types_of_t, _Sequence>) + requires(!stdexec::__merror<_Sequence>) && (!stdexec::__mvalid<__item_types_of_t, _Sequence>) auto __check_sequence(_Sequence*) -> stdexec::__mexception< _SEQUENCE_GET_ITEM_TYPES_IS_NOT_WELL_FORMED_, _WITH_SEQUENCE_<_Sequence> >; template - requires (!stdexec::__merror<_Sequence>) - && stdexec::__mvalid<__item_types_of_t, _Sequence> - auto __check_sequence(_Sequence*) -> decltype( - exec::__check_items<_Sequence>(static_cast<__item_types_of_t<_Sequence>*>(nullptr))); + requires(!stdexec::__merror<_Sequence>) && stdexec::__mvalid<__item_types_of_t, _Sequence> + auto __check_sequence(_Sequence*) -> decltype(exec::__check_items<_Sequence>( + static_cast<__item_types_of_t<_Sequence>*>(nullptr))); template concept __well_formed_item_senders = has_sequence_item_types> - && requires(stdexec::__decay_t<_Sequence>* __sequence) { - { exec::__check_sequence(__sequence) } -> stdexec::__ok; - }; + && requires(stdexec::__decay_t<_Sequence>* __sequence) { + { exec::__check_sequence(__sequence) } -> stdexec::__ok; + }; template concept __well_formed_sequence_sender = stdexec::__well_formed_sender<_Sequence> - && enable_sequence_sender> - && __well_formed_item_senders<_Sequence>; + && enable_sequence_sender> + && __well_formed_item_senders<_Sequence>; template struct _WITH_RECEIVER_ { }; @@ -422,8 +425,7 @@ namespace exec { stdexec::env_of_t<_Receiver> > >) - || (!sequence_sender_in<_Sequence, stdexec::env_of_t<_Receiver>> - && stdexec::__receiver_from<__sequence_sndr::__stopped_means_break_t<_Receiver>, next_sender_of_t<_Receiver, _Sequence>>) ); + || (!sequence_sender_in<_Sequence, stdexec::env_of_t<_Receiver>> && stdexec::__receiver_from<__sequence_sndr::__stopped_means_break_t<_Receiver>, next_sender_of_t<_Receiver, _Sequence>>) ); namespace __sequence_sndr { struct subscribe_t; @@ -447,32 +449,32 @@ namespace exec { && sender_to, __stopped_means_break_t<_Receiver>>; template - concept __subscribable_with_static_member = receiver<_Receiver> - && sequence_sender_in<_Sequence, env_of_t<_Receiver>> - && sequence_receiver_from<_Receiver, _Sequence> - && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { - { - STDEXEC_REMOVE_REFERENCE(_Sequence) - ::subscribe(static_cast<_Sequence &&>(__sequence), static_cast<_Receiver &&>(__rcvr)) - }; - }; + concept __subscribable_with_static_member = + receiver<_Receiver> && sequence_sender_in<_Sequence, env_of_t<_Receiver>> + && sequence_receiver_from<_Receiver, _Sequence> + && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { + { + STDEXEC_REMOVE_REFERENCE(_Sequence) + ::subscribe(static_cast<_Sequence&&>(__sequence), static_cast<_Receiver&&>(__rcvr)) + }; + }; template concept __subscribable_with_member = receiver<_Receiver> - && sequence_sender_in<_Sequence, env_of_t<_Receiver>> - && sequence_receiver_from<_Receiver, _Sequence> - && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { - { - static_cast<_Sequence &&>(__sequence) - .subscribe(static_cast<_Receiver &&>(__rcvr)) - }; - }; + && sequence_sender_in<_Sequence, env_of_t<_Receiver>> + && sequence_receiver_from<_Receiver, _Sequence> + && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { + { + static_cast<_Sequence&&>(__sequence) + .subscribe(static_cast<_Receiver&&>(__rcvr)) + }; + }; template concept __subscribable_with_tag_invoke = receiver<_Receiver> - && sequence_sender_in<_Sequence, env_of_t<_Receiver>> - && sequence_receiver_from<_Receiver, _Sequence> - && tag_invocable; + && sequence_sender_in<_Sequence, env_of_t<_Receiver>> + && sequence_receiver_from<_Receiver, _Sequence> + && tag_invocable; struct subscribe_t { template @@ -499,18 +501,19 @@ namespace exec { >; return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); } else if constexpr (__subscribable_with_static_member<__tfx_sequence_t, _Receiver>) { - using __result_t = decltype(STDEXEC_REMOVE_REFERENCE(__tfx_sequence_t):: - subscribe(__declval<__tfx_sequence_t>(), __declval<_Receiver>())); + using __result_t = decltype(STDEXEC_REMOVE_REFERENCE( + __tfx_sequence_t)::subscribe(__declval<__tfx_sequence_t>(), __declval<_Receiver>())); static_assert( operation_state<__result_t>, "Sequence::subscribe(sender, receiver) must return a type that " "satisfies the operation_state concept"); constexpr bool _Nothrow = _NothrowTfxSequence - && noexcept(STDEXEC_REMOVE_REFERENCE(__tfx_sequence_t) - ::subscribe(__declval<__tfx_sequence_t>(), __declval<_Receiver>())); + && noexcept(STDEXEC_REMOVE_REFERENCE(__tfx_sequence_t)::subscribe( + __declval<__tfx_sequence_t>(), __declval<_Receiver>())); return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); } else if constexpr (__subscribable_with_member<__tfx_sequence_t, _Receiver>) { - using __result_t = decltype(__declval<__tfx_sequence_t>().subscribe(__declval<_Receiver>())); + using __result_t = decltype(__declval<__tfx_sequence_t>() + .subscribe(__declval<_Receiver>())); static_assert( operation_state<__result_t>, "Sequence::subscribe(sender, receiver) must return a type that " @@ -548,17 +551,17 @@ namespace exec { auto __domain = __get_late_domain(__sequence, __env); if constexpr (__next_connectable<__tfx_sequence_t, _Receiver>) { next_sender_of_t<_Receiver, __tfx_sequence_t> __next = set_next( - __rcvr, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); + __rcvr, + stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); return stdexec::connect( static_cast&&>(__next), __stopped_means_break_t<_Receiver>{static_cast<_Receiver&&>(__rcvr)}); // NOLINTNEXTLINE(bugprone-branch-clone) } else if constexpr (__subscribable_with_static_member<__tfx_sequence_t, _Receiver>) { - auto&& __tfx_sequence = transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); - return __tfx_sequence - .subscribe( - static_cast<__tfx_sequence_t&&>(__tfx_sequence), - static_cast<_Receiver&&>(__rcvr)); + auto&& __tfx_sequence = + transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); + return __tfx_sequence.subscribe( + static_cast<__tfx_sequence_t&&>(__tfx_sequence), static_cast<_Receiver&&>(__rcvr)); } else if constexpr (__subscribable_with_member<__tfx_sequence_t, _Receiver>) { return stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env) .subscribe(static_cast<_Receiver&&>(__rcvr)); @@ -570,16 +573,16 @@ namespace exec { } else if constexpr (enable_sequence_sender>) { // This should generate an instantiate backtrace that contains useful // debugging information. - auto&& __tfx_sequence = transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); - return __tfx_sequence - .subscribe( - static_cast<__tfx_sequence_t&&>(__tfx_sequence), - static_cast<_Receiver&&>(__rcvr)); + auto&& __tfx_sequence = + transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); + return __tfx_sequence.subscribe( + static_cast<__tfx_sequence_t&&>(__tfx_sequence), static_cast<_Receiver&&>(__rcvr)); } else { // This should generate an instantiate backtrace that contains useful // debugging information. next_sender_of_t<_Receiver, __tfx_sequence_t> __next = set_next( - __rcvr, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); + __rcvr, + stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); return stdexec::connect( static_cast&&>(__next), __stopped_means_break_t<_Receiver>{static_cast<_Receiver&&>(__rcvr)}); @@ -604,9 +607,10 @@ namespace exec { template concept sequence_sender_to = - sequence_receiver_from<_Receiver, _Sequence> && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { - subscribe(static_cast<_Sequence &&>(__sequence), static_cast<_Receiver &&>(__rcvr)); - }; + sequence_receiver_from<_Receiver, _Sequence> + && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { + subscribe(static_cast<_Sequence&&>(__sequence), static_cast<_Receiver&&>(__rcvr)); + }; template concept __stoppable_receiver = stdexec::__callable @@ -632,37 +636,37 @@ namespace exec { } //////////////////////////////////////////////////////////////////////////////// -# define STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR \ - "\n" \ - "\n" \ - "Trying to compute the sequences's item types resulted in an error. See\n" \ - "the rest of the compiler diagnostic for clues. Look for the string \"_ERROR_\".\n" - -# define STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE \ - "\n" \ - "\n" \ - "The member function `get_item_types` of the sequence returned an\n" \ - "invalid type.\n" \ - "\n" \ - "A sender's `get_item_types` function must return a specialization of\n" \ - "`exec::item_types<...>`, as follows:\n" \ - "\n" \ - " class MySequence\n" \ - " {\n" \ - " public:\n" \ - " using sender_concept = exec::sequence_sender_t;\n" \ - "\n" \ - " template \n" \ - " auto get_item_types(_Env&&...) -> exec::item_types<\n" \ - " // This sequence produces void items...\n" \ - " stdexec::__call_result_t>\n" \ - " {\n" \ - " return {};\n" \ - " }\n" \ - " ...\n" \ - " };\n" - - // Used to report a meaningful error message when the sender_in +#define STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR \ + "\n" \ + "\n" \ + "Trying to compute the sequences's item types resulted in an error. See\n" \ + "the rest of the compiler diagnostic for clues. Look for the string \"_ERROR_\".\n" + +#define STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE \ + "\n" \ + "\n" \ + "The member function `get_item_types` of the sequence returned an\n" \ + "invalid type.\n" \ + "\n" \ + "A sender's `get_item_types` function must return a specialization of\n" \ + "`exec::item_types<...>`, as follows:\n" \ + "\n" \ + " class MySequence\n" \ + " {\n" \ + " public:\n" \ + " using sender_concept = exec::sequence_sender_t;\n" \ + "\n" \ + " template \n" \ + " auto get_item_types(_Env&&...) -> exec::item_types<\n" \ + " // This sequence produces void items...\n" \ + " stdexec::__call_result_t>\n" \ + " {\n" \ + " return {};\n" \ + " }\n" \ + " ...\n" \ + " };\n" + + // Used to report a meaningful error message when the sender_in // concept check fails. template auto __diagnose_sequence_concept_failure() { @@ -676,15 +680,20 @@ namespace exec { "bug in the sequence implementation."); } else if constexpr (!std::move_constructible>) { static_assert( - std::move_constructible>, "The sequence type is not move-constructible."); + std::move_constructible>, + "The sequence type is not move-constructible."); } else if constexpr (!std::constructible_from, _Sequence>) { static_assert( std::constructible_from, _Sequence>, "The sequence cannot be decay-copied. Did you forget a std::move?"); } else { using __items_t = __item_types_of_t<_Sequence, _Env...>; - if constexpr (stdexec::__same_as<__items_t, __sequence_sndr::__unrecognized_sequence_error_t<_Sequence, _Env...>>) { - static_assert(stdexec::__mnever<__items_t>, STDEXEC_ERROR_CANNOT_COMPUTE_COMPLETION_SIGNATURES); + if constexpr (stdexec::__same_as< + __items_t, + __sequence_sndr::__unrecognized_sequence_error_t<_Sequence, _Env...> + >) { + static_assert( + stdexec::__mnever<__items_t>, STDEXEC_ERROR_CANNOT_COMPUTE_COMPLETION_SIGNATURES); } else if constexpr (stdexec::__merror<__items_t>) { static_assert( !stdexec::__merror<__items_t>, STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR); @@ -719,7 +728,12 @@ namespace exec { }; template - struct __debug_sequence_sender_receiver<_CvrefSequenceId, _Env, stdexec::completion_signatures<_Sigs...>, item_types<_Items...>> + struct __debug_sequence_sender_receiver< + _CvrefSequenceId, + _Env, + stdexec::completion_signatures<_Sigs...>, + item_types<_Items...> + > : __valid_completions<__normalize_sig_t<_Sigs>...> , __valid_next<_Items...> { using __t = __debug_sequence_sender_receiver; @@ -737,8 +751,15 @@ namespace exec { if constexpr (sequence_sender_in<_Sequence, _Env>) { using __sigs_t = stdexec::__completion_signatures_of_t<_Sequence, __debug_env_t<_Env>>; using __item_types_t = __sequence_sndr::__item_types_of_t<_Sequence, __debug_env_t<_Env>>; - using __receiver_t = __debug_sequence_sender_receiver, _Env, __sigs_t, __item_types_t>; - if constexpr (!std::same_as<__sigs_t, __debug::__completion_signatures> || !std::same_as<__item_types_t, __debug::__item_types>) { + using __receiver_t = __debug_sequence_sender_receiver< + stdexec::__cvref_id<_Sequence>, + _Env, + __sigs_t, + __item_types_t + >; + if constexpr ( + !std::same_as<__sigs_t, __debug::__completion_signatures> + || !std::same_as<__item_types_t, __debug::__item_types>) { using __operation_t = exec::subscribe_result_t<_Sequence, __receiver_t>; //static_assert(receiver_of<_Receiver, _Sigs>); if constexpr (!std::same_as<__operation_t, __debug_operation>) { @@ -756,24 +777,25 @@ namespace exec { } // namespace __debug using __debug::__debug_sequence_sender; - #if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() +#if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() // __checked_completion_signatures is for catching logic bugs in a sender's metadata. If sender // and sender_in are both true, then they had better report the same metadata. This // completion signatures wrapper enforces that at compile time. template - auto __checked_item_types(_Sequence && __sequence, _Env &&... __env) noexcept { + auto __checked_item_types(_Sequence&& __sequence, _Env&&... __env) noexcept { using __completions_t = decltype(get_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); // (void)__sequence; // [](auto&&...){}(__env...); - exec::__debug_sequence_sender(static_cast<_Sequence &&>(__sequence), __env...); + exec::__debug_sequence_sender(static_cast<_Sequence&&>(__sequence), __env...); return __completions_t{}; } template requires sequence_sender_in<_Sequence, _Env...> - using item_types_of_t = - decltype(exec::__checked_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); + using item_types_of_t = decltype(exec::__checked_item_types( + stdexec::__declval<_Sequence>(), + stdexec::__declval<_Env>()...)); #else template requires sequence_sender_in<_Sequence, _Env...> diff --git a/test/exec/sequence/test_merge.cpp b/test/exec/sequence/test_merge.cpp index 89e8fd532..f742a16e0 100644 --- a/test/exec/sequence/test_merge.cpp +++ b/test/exec/sequence/test_merge.cpp @@ -41,41 +41,44 @@ namespace { -struct null_receiver { - using __id = null_receiver; - using __t = null_receiver; - using receiver_concept = ex::receiver_t; + struct null_receiver { + using __id = null_receiver; + using __t = null_receiver; + using receiver_concept = ex::receiver_t; - void set_value() noexcept { - } + void set_value() noexcept { + } - template - void set_error(_Error&& ) noexcept { - } + template + void set_error(_Error&&) noexcept { + } - void set_stopped() noexcept { - } + void set_stopped() noexcept { + } - [[nodiscard]] - auto get_env() const noexcept -> ex::env<> { - return {}; - } + [[nodiscard]] + auto get_env() const noexcept -> ex::env<> { + return {}; + } - struct ignore_values_fn_t { - template - void operator()(_Vs&&...) const noexcept {} - }; + struct ignore_values_fn_t { + template + void operator()(_Vs&&...) const noexcept { + } + }; - template - [[nodiscard]] - auto set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) - -> stdexec::__call_result_t, - ignore_values_fn_t> { - return stdexec::upon_error(stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); - } -}; + template + [[nodiscard]] + auto set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) + -> stdexec::__call_result_t< + stdexec::upon_error_t, + stdexec::__call_result_t, + ignore_values_fn_t + > { + return stdexec::upon_error( + stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); + } + }; TEST_CASE( "merge - merge two sequence senders of no elements", @@ -91,15 +94,14 @@ struct null_receiver { "merge - merge three sequence senders of no elements", "[sequence_senders][merge][empty_sequence]") { int counter = 0; - auto merged = exec::merge(exec::empty_sequence(), exec::empty_sequence(), exec::empty_sequence()); + auto merged = + exec::merge(exec::empty_sequence(), exec::empty_sequence(), exec::empty_sequence()); auto op = exec::subscribe(merged, null_receiver{}); ex::start(op); CHECK(counter == 0); } - TEST_CASE( - "merge - merge sender of 2 senders", - "[sequence_senders][merge]") { + TEST_CASE("merge - merge sender of 2 senders", "[sequence_senders][merge]") { int value = 0; int count = 0; auto merged = exec::merge(ex::just(84), ex::just(-42)); @@ -131,9 +133,7 @@ struct null_receiver { } #if STDEXEC_HAS_STD_RANGES() - TEST_CASE( - "merge - merge sender merges all items", - "[sequence_senders][merge][iterate]") { + TEST_CASE("merge - merge sender merges all items", "[sequence_senders][merge][iterate]") { auto range = [](auto from, auto to) { return exec::iterate(std::views::iota(from, to)); }; @@ -149,17 +149,16 @@ struct null_receiver { std::ptrdiff_t max = 0; auto sum = exec::merge(range(100, 120), range(200, 220), range(300, 320)) | then_each([&total, &count, &max](int x) noexcept { - std::ptrdiff_t current = 0; - current = std::abs(reinterpret_cast(¤t) - reinterpret_cast(&max)); - max = current > max ? current : max; - UNSCOPED_INFO("item: " << x << ", stack size: " << current); - total += x; - ++count; - }); + std::ptrdiff_t current = 0; + current = std::abs( + reinterpret_cast(¤t) - reinterpret_cast(&max)); + max = current > max ? current : max; + UNSCOPED_INFO("item: " << x << ", stack size: " << current); + total += x; + ++count; + }); // this causes both iterate sequences to use the same trampoline. - ex::sync_wait(exec::sequence( - stdexec::schedule(sched), - exec::ignore_all_values(sum))); + ex::sync_wait(exec::sequence(stdexec::schedule(sched), exec::ignore_all_values(sum))); UNSCOPED_INFO("max stack size: " << max); CHECK(total == 12570); CHECK(count == 60); @@ -194,25 +193,22 @@ struct null_receiver { range(200, 220) | continues_each_on(sched1), range(300, 320) | continues_each_on(sched2)) | then_each([](int x) noexcept { - // runs on sched0 and sched1 and sched2 in parallel. - // access to shared data would need to be protected - return std::make_tuple(x, std::this_thread::get_id()); - }) - | continues_each_on(sched3) - | then_each([&total, &count](auto v) { - // runs only on sched3, which is a strand (a static - // pool with one thread) - // it is safe to use shared data here - auto [x, id] = v; - total += x; - ++count; - UNSCOPED_INFO("item: " << x - << ", from thread id: " << id - << ", on thread id: " << std::this_thread::get_id()); - }); - ex::sync_wait(exec::sequence( - ex::schedule(sched3), - exec::ignore_all_values(sum))); + // runs on sched0 and sched1 and sched2 in parallel. + // access to shared data would need to be protected + return std::make_tuple(x, std::this_thread::get_id()); + }) + | continues_each_on(sched3) | then_each([&total, &count](auto v) { + // runs only on sched3, which is a strand (a static + // pool with one thread) + // it is safe to use shared data here + auto [x, id] = v; + total += x; + ++count; + UNSCOPED_INFO( + "item: " << x << ", from thread id: " << id + << ", on thread id: " << std::this_thread::get_id()); + }); + ex::sync_wait(exec::sequence(ex::schedule(sched3), exec::ignore_all_values(sum))); CHECK(total == 12570); CHECK(count == 60); } @@ -234,11 +230,13 @@ struct null_receiver { auto with_scheduler = ex::write_env(ex::prop{ex::get_scheduler, inline_scheduler()}); auto adaptor = ex::on(sched, ex::then([](std::string x) { return x + ", world"; })) | with_scheduler; - auto snd = exec::merge( - start | exec::transform_each(adaptor), - start | exec::transform_each(adaptor)) - | exec::transform_each(ex::then([&](int x) { result += x; ++count; })) - | exec::ignore_all_values(); + auto snd = + exec::merge(start | exec::transform_each(adaptor), start | exec::transform_each(adaptor)) + | exec::transform_each(ex::then([&](int x) { + result += x; + ++count; + })) + | exec::ignore_all_values(); ex::sync_wait(snd); CHECK(result == 42); CHECK(count == 2); From f58736e52f51abab5d18a247e0890439c6ff6298 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Wed, 29 Oct 2025 20:31:44 +0000 Subject: [PATCH 22/39] remove _t from concept name --- include/exec/sequence_senders.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 10cbf81ca..a3ff99556 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -37,14 +37,14 @@ namespace exec { using __mall_contained_in_t = __mapply<__mall_contained_in_impl<_Haystack>, _Needles>; template - concept __all_contained_in_t = __v<__mall_contained_in_t<_Needles, _Haystack>>; + concept __all_contained_in = __v<__mall_contained_in_t<_Needles, _Haystack>>; } // namespace __sequence_sndr // This concept checks if a given sender satisfies the requirements to be returned from `set_next`. template > concept next_sender = stdexec::sender_in<_Sender, _Env> - && __sequence_sndr::__all_contained_in_t< + && __sequence_sndr::__all_contained_in< stdexec::completion_signatures_of_t<_Sender, _Env>, stdexec::completion_signatures >; From 431c67ca71170d816394360f1a72e5baf6a0867f Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Wed, 29 Oct 2025 13:48:29 -0700 Subject: [PATCH 23/39] simplify meta expression at the behest of Visual Studio --- include/exec/sequence/merge_each.hpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/include/exec/sequence/merge_each.hpp b/include/exec/sequence/merge_each.hpp index be54b880a..257c5a723 100644 --- a/include/exec/sequence/merge_each.hpp +++ b/include/exec/sequence/merge_each.hpp @@ -619,12 +619,14 @@ namespace exec { } void nested_sequence_complete() noexcept override { + auto __op = __op_; stdexec::set_value(static_cast<_NextReceiver&&>(this->__receiver_)); - __op_->nested_sequence_complete(); + __op->nested_sequence_complete(); } void nested_sequence_break() noexcept override { + auto __op = __op_; stdexec::set_stopped(static_cast<_NextReceiver&&>(this->__receiver_)); - __op_->nested_sequence_break(); + __op->nested_sequence_break(); } }; @@ -708,14 +710,18 @@ namespace exec { template struct __arg_of_t { + template - using __f = - stdexec::__meval && ...), - stdexec::__q, - __value_completions_error<_Sequence, _Sender, _Env...> - >::template __f, _Args...> - ; + static constexpr bool __valid_args = sizeof...(_Args) == 1 && (has_sequence_item_types<_Args, _Env...> && ... && true); + + template + using __checked_eval_t = stdexec::__if_c< + __valid_args<_Args...>, + stdexec::__types<_Args...>, + __value_completions_error<_Sequence, _Sender, _Env...>>; + + template + using __f = __checked_eval_t<_Args...>; }; template From 825110f931dfe8c2dae03ded60fa77610685c141 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Wed, 29 Oct 2025 13:49:32 -0700 Subject: [PATCH 24/39] compile time debug changes --- include/exec/sequence_senders.hpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 8a844fdb4..a8be581dc 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -22,11 +22,18 @@ #include "stdexec/__detail/__diagnostics.hpp" namespace exec { + namespace __errs { + template + struct _WITH_SEQUENCE_; + + template + struct _WITH_SEQUENCES_; + } template - struct _WITH_SEQUENCE_; + using _WITH_SEQUENCE_ = __errs::_WITH_SEQUENCE_>; template - struct _WITH_SEQUENCES_; + using _WITH_SEQUENCES_ = __errs::_WITH_SEQUENCES_...>; struct sequence_sender_t : stdexec::sender_t { }; @@ -785,13 +792,13 @@ namespace exec { static_assert( __well_formed_item_senders<_Sequence>, STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE); - } else { - stdexec::__diagnose_sender_concept_failure<_Sequence, _Env...>(); + //} else { + // stdexec::__diagnose_sender_concept_failure<_Sequence, _Env...>(); } #if STDEXEC_MSVC() || STDEXEC_NVHPC() // MSVC and NVHPC need more encouragement to print the type of the // error. - _Completions __what = 0; +// _Completions __what = 0; #endif } } From 9c71ef23e9e5f63279845cbae85cbc5abdba95d3 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Fri, 31 Oct 2025 08:48:52 -0700 Subject: [PATCH 25/39] fix crash in delays_each_on --- test/exec/sequence/test_merge_each.cpp | 8 ++++-- .../sequence/test_merge_each_threaded.cpp | 28 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/test/exec/sequence/test_merge_each.cpp b/test/exec/sequence/test_merge_each.cpp index 34a77a534..188da5408 100644 --- a/test/exec/sequence/test_merge_each.cpp +++ b/test/exec/sequence/test_merge_each.cpp @@ -94,9 +94,11 @@ namespace { // a sequence adaptor that schedules each item to complete // on the specified scheduler after the specified duration [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { - return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { - return sequence(schedule_after(sched, after), stdexec::just(vs...)); - })); + auto delay_value = [](Value&& value, Sched sched, duration_of_t after){ + return sequence(schedule_after(sched, after), static_cast(value)); + }; + auto delay_adaptor = stdexec::__binder_back>{{sched, after}, {}, {}}; + return exec::transform_each(delay_adaptor); }; // a sequence adaptor that applies a function to each item // the function must produce a sequence diff --git a/test/exec/sequence/test_merge_each_threaded.cpp b/test/exec/sequence/test_merge_each_threaded.cpp index 61cab3470..7f8266881 100644 --- a/test/exec/sequence/test_merge_each_threaded.cpp +++ b/test/exec/sequence/test_merge_each_threaded.cpp @@ -22,7 +22,7 @@ #include "exec/sequence/iterate.hpp" #include "exec/sequence_senders.hpp" #include "exec/variant_sender.hpp" -#include "exec/static_thread_pool.hpp" +#include "exec/single_thread_context.hpp" #include "exec/timed_thread_scheduler.hpp" #include "stdexec/__detail/__meta.hpp" #include "stdexec/__detail/__read_env.hpp" @@ -93,10 +93,12 @@ namespace { }; // a sequence adaptor that schedules each item to complete // on the specified scheduler after the specified duration - [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { - return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { - return sequence(schedule_after(sched, after), stdexec::just(vs...)); - })); + [[maybe_unused]] static constexpr auto delays_each_on = [](Sched sched, duration_of_t after) noexcept { + auto delay_value = [](Value&& value, Sched sched, duration_of_t after){ + return sequence(schedule_after(sched, after), static_cast(value)); + }; + auto delay_adaptor = stdexec::__binder_back>{{sched, after}, {}, {}}; + return exec::transform_each(delay_adaptor); }; // a sequence adaptor that applies a function to each item // the function must produce a sequence @@ -178,13 +180,13 @@ namespace { TEST_CASE( "merge_each - merge_each sender merges all items from multiple threads", - "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { + "[sequence_senders][single_thread_context][merge_each][merge][iterate]") { - exec::static_thread_pool ctx0{1}; + exec::single_thread_context ctx0; ex::scheduler auto sched0 = ctx0.get_scheduler(); - exec::static_thread_pool ctx1{1}; + exec::single_thread_context ctx1; ex::scheduler auto sched1 = ctx1.get_scheduler(); - exec::static_thread_pool ctx2{1}; + exec::single_thread_context ctx2; ex::scheduler auto sched2 = ctx2.get_scheduler(); auto sequences = merge( @@ -208,9 +210,9 @@ namespace { TEST_CASE( "merge_each - merge_each sender stops on failed item while merging all items from multiple threads", - "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { + "[sequence_senders][single_thread_context][merge_each][merge][iterate]") { - exec::static_thread_pool ctx0{1}; + exec::single_thread_context ctx0; ex::scheduler auto sched0 = ctx0.get_scheduler(); exec::timed_thread_context ctx1; ex::scheduler auto sched1 = ctx1.get_scheduler(); @@ -281,9 +283,9 @@ namespace { TEST_CASE( "merge_each - merge_each sender stops while merging all items from multiple threads", - "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { + "[sequence_senders][single_thread_context][merge_each][merge][iterate]") { - exec::static_thread_pool ctx0{1}; + exec::single_thread_context ctx0; ex::scheduler auto sched0 = ctx0.get_scheduler(); exec::timed_thread_context ctx1; ex::scheduler auto sched1 = ctx1.get_scheduler(); From 36b00e293284ae2e71ae91c6cb7efb93be1f7bbf Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Fri, 31 Oct 2025 09:39:19 -0700 Subject: [PATCH 26/39] clang-format changes --- include/exec/sequence/merge_each.hpp | 506 ++++++++++-------- include/exec/sequence_senders.hpp | 147 ++--- test/exec/sequence/test_merge_each.cpp | 314 +++++------ .../sequence/test_merge_each_threaded.cpp | 283 +++++----- test/exec/test_sequence_senders.cpp | 4 +- 5 files changed, 671 insertions(+), 583 deletions(-) diff --git a/include/exec/sequence/merge_each.hpp b/include/exec/sequence/merge_each.hpp index 257c5a723..a379a818e 100644 --- a/include/exec/sequence/merge_each.hpp +++ b/include/exec/sequence/merge_each.hpp @@ -45,26 +45,24 @@ namespace exec { auto operator()(inplace_stop_token& __stop_token) const noexcept { return stdexec::prop{stdexec::get_stop_token, __stop_token}; } - template + template auto operator()(stdexec::inplace_stop_token& __stop_token, _Env&& __env) const noexcept { return __env::__join((*this)(__stop_token), static_cast<_Env&&>(__env)); } auto operator()(inplace_stop_source& __stop_source) const noexcept { return stdexec::prop{stdexec::get_stop_token, __stop_source.get_token()}; } - template + template auto operator()(stdexec::inplace_stop_source& __stop_source, _Env&& __env) const noexcept { return __env::__join((*this)(__stop_source), static_cast<_Env&&>(__env)); } }; static constexpr inline __env_with_inplace_stop_token_t __env_with_inplace_stop_token; - template - using __env_with_inplace_stop_token_result_t = decltype( - __env_with_inplace_stop_token( - stdexec::__declval(), - stdexec::__declval<_Env>()...) - ); + template + using __env_with_inplace_stop_token_result_t = decltype(__env_with_inplace_stop_token( + stdexec::__declval(), + stdexec::__declval<_Env>()...)); template struct __nested_stop { @@ -80,8 +78,9 @@ namespace exec { using __env_result_t = __env_with_inplace_stop_token_result_t<__env_t>; using __stop_token_t = stop_token_of_t<__env_t>; using __stop_callback_t = stop_callback_for_t<__stop_token_t, __on_stop_request>; - struct no_callback {}; - using __callback_t = __if_c, no_callback, __optional<__stop_callback_t>>; + struct no_callback { }; + using __callback_t = + __if_c, no_callback, __optional<__stop_callback_t>>; inplace_stop_source __stop_source_{}; __callback_t __on_stop_{}; @@ -114,7 +113,7 @@ namespace exec { return false; } - inplace_stop_token get_token() const& noexcept { + inplace_stop_token get_token() const & noexcept { return __stop_source_.get_token(); } @@ -128,8 +127,7 @@ namespace exec { return env_from(get_env(__receiver)); } - using env_t = - decltype(__env_from(std::declval<__nested_stop*>(), __declval<__env_t>())); + using env_t = decltype(__env_from(std::declval<__nested_stop*>(), __declval<__env_t>())); }; template @@ -149,9 +147,10 @@ namespace exec { // The first error to arrive will request_stop on the nested inplace_stop_source // - template + template struct __operation_base_interface { - ~__operation_base_interface(){} + ~__operation_base_interface() { + } virtual void nested_value_started() noexcept = 0; virtual void nested_value_complete() noexcept = 0; virtual bool nested_value_fail() noexcept = 0; @@ -163,22 +162,27 @@ namespace exec { __operation_base_interface(_ErrorStorage* __error_storage) noexcept : __error_storage_{__error_storage} - , __token_{} {} + , __token_{} { + } - template + template auto env_from(_Env&& __env) noexcept -> __env_with_inplace_stop_token_result_t<_Env> { return __env_with_inplace_stop_token(__token_, static_cast<_Env&&>(__env)); } - template - requires (!same_as<_Error, _ErrorStorage>) - void store_error(_Error&& __error) - noexcept( - __nothrow_callable), _ErrorStorage&, _Error>) { + template + requires(!same_as<_Error, _ErrorStorage>) + void + store_error(_Error&& __error) noexcept(__nothrow_callable< + decltype(&_ErrorStorage::template emplace<_Error>), + _ErrorStorage&, + _Error + >) { if (this->nested_value_fail()) { // We are the first child to complete with an error, so we must save the error. (Any // subsequent errors are ignored.) - if constexpr (noexcept(__error_storage_->template emplace<_Error>(static_cast<_Error&&>(__error)))) { + if constexpr (noexcept(__error_storage_ + ->template emplace<_Error>(static_cast<_Error&&>(__error)))) { __error_storage_->template emplace<_Error>(static_cast<_Error&&>(__error)); } else { STDEXEC_TRY { @@ -215,43 +219,43 @@ namespace exec { stdexec::set_error( static_cast<_ErrorReceiver&&>(__receiver_), static_cast(__error)); - } - , static_cast<_ErrorStorage&&>(*__op_->__error_storage_)); + }, + static_cast<_ErrorStorage&&>(*__op_->__error_storage_)); } }; }; - template + template struct __error_sender { using __operation_base_interface_t = __operation_base_interface<_ErrorStorage>; struct __t { using __id = __error_sender; using sender_concept = stdexec::sender_t; - template + template using __error_op_t = stdexec::__t<__error_op<_ErrorReceiverId, _ErrorStorage>>; - template + template using __error_signature_t = stdexec::set_error_t(_Error); __operation_base_interface_t* __op_; template _Self, class... _Env> - static auto get_completion_signatures(_Self&&, _Env&&...) noexcept - -> stdexec::__mapply< - stdexec::__mtransform< - stdexec::__q<__error_signature_t>, - stdexec::__qq>, - _ErrorStorage> { + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept -> stdexec::__mapply< + stdexec::__mtransform< + stdexec::__q<__error_signature_t>, + stdexec::__qq + >, + _ErrorStorage + > { return {}; } template _Self, receiver _ErrorReceiver> static auto connect(_Self&& __self, _ErrorReceiver&& __rcvr) noexcept(__nothrow_move_constructible<_ErrorReceiver>) - -> __error_op_t> { - return {static_cast<_ErrorReceiver&&>(__rcvr), - __self.__op_}; + -> __error_op_t> { + return {static_cast<_ErrorReceiver&&>(__rcvr), __self.__op_}; } }; }; @@ -288,8 +292,7 @@ namespace exec { _Receiver* __receiver_; __nested_stop_t* __source_; - auto operator()() const noexcept - -> __nested_stop_env_t { + auto operator()() const noexcept -> __nested_stop_env_t { return __source_->env_from(*__receiver_); } }; @@ -306,7 +309,8 @@ namespace exec { using __error_next_sender_t = next_sender_of_t<_Receiver&, __error_sender_t>; using __env_fn_t = __env_fn<_Receiver>; using __error_next_receiver_t = __error_next_receiver<_ErrorStorage, __env_fn_t>; - using __error_op_t = stdexec::connect_result_t<__error_next_sender_t, __error_next_receiver_t>; + using __error_op_t = + stdexec::connect_result_t<__error_next_sender_t, __error_next_receiver_t>; _Receiver __receiver_; _ErrorStorage __error_storage_{}; @@ -316,14 +320,13 @@ namespace exec { __nested_stop_t __nested_stop_{}; stdexec::__optional<__error_op_t> __error_op_{}; - __operation_base(_Receiver __receiver) - noexcept(__nothrow_move_constructible<_Receiver>) + __operation_base(_Receiver __receiver) noexcept(__nothrow_move_constructible<_Receiver>) : __interface_t{&__error_storage_} , __receiver_{static_cast<_Receiver&&>(__receiver)} { - __interface_t::__token_ = __nested_stop_.get_token(); - } + __interface_t::__token_ = __nested_stop_.get_token(); + } - template + template void set_exception(_Error&& __error) noexcept { switch (__completion_.exchange(__completion_t::__error)) { case __completion_t::__started: @@ -355,7 +358,8 @@ namespace exec { // already been requested. __nested_stop_.request_stop(); break; - case __completion_t::__stopped: [[fallthrough]]; // We're already in the "stopped" state. Ignore the break. + case __completion_t::__stopped: + [[fallthrough]]; // We're already in the "stopped" state. Ignore the break. case __completion_t::__error:; // We're already in the "error" state. Ignore the break. } } @@ -421,33 +425,39 @@ namespace exec { case __completion_t::__error: if (__ex_ != nullptr) { // forward error from the subscribed sequence of sequences - stdexec::set_error(static_cast<_Receiver&&>(__receiver_), static_cast(__ex_)); + stdexec::set_error( + static_cast<_Receiver&&>(__receiver_), static_cast(__ex_)); } else { // forward error from the nested sequences as the last item if constexpr ( __nothrow_callable && __nothrow_connectable<__error_next_sender_t, __error_next_receiver_t>) { auto __next_sender = exec::set_next(__receiver_, __error_sender_t{this}); - auto __next_receiver = __error_next_receiver_t{this, __env_fn{&__receiver_, &__nested_stop_}}; + auto __next_receiver = __error_next_receiver_t{ + this, __env_fn{&__receiver_, &__nested_stop_} + }; __error_op_.__emplace_from([&]() { - return stdexec::connect( - static_cast<__error_next_sender_t&&>(__next_sender), - static_cast<__error_next_receiver_t&&>(__next_receiver)); - }); + return stdexec::connect( + static_cast<__error_next_sender_t&&>(__next_sender), + static_cast<__error_next_receiver_t&&>(__next_receiver)); + }); stdexec::start(__error_op_.value()); } else { STDEXEC_TRY { auto __next_sender = exec::set_next(__receiver_, __error_sender_t{this}); - auto __next_receiver = __error_next_receiver_t{this, __env_fn{&__receiver_, &__nested_stop_}}; + auto __next_receiver = __error_next_receiver_t{ + this, __env_fn{&__receiver_, &__nested_stop_} + }; __error_op_.__emplace_from([&]() { - return stdexec::connect( - static_cast<__error_next_sender_t&&>(__next_sender), - static_cast<__error_next_receiver_t&&>(__next_receiver)); - }); + return stdexec::connect( + static_cast<__error_next_sender_t&&>(__next_sender), + static_cast<__error_next_receiver_t&&>(__next_receiver)); + }); stdexec::start(__error_op_.value()); } STDEXEC_CATCH_ALL { - stdexec::set_error(static_cast<_Receiver&&>(__receiver_), std::current_exception()); + stdexec::set_error( + static_cast<_Receiver&&>(__receiver_), std::current_exception()); } } } @@ -487,7 +497,7 @@ namespace exec { __nested_value_operation_base<_NestedValueReceiver>* __nested_value_op_; __operation_base_interface_t* __op_; - template + template void set_value(_Results&&... __results) noexcept { auto __op = __op_; stdexec::set_value( @@ -530,13 +540,19 @@ namespace exec { __nested_value_op_t __nested_value_op_; __operation_base_interface_t* __op_; - __t(_NestedValueReceiver __rcvr, _NestedValueSender __result, __operation_base_interface_t* __op) + __t( + _NestedValueReceiver __rcvr, + _NestedValueSender __result, + __operation_base_interface_t* __op) noexcept( __nothrow_move_constructible<_NestedValueReceiver> && __nothrow_connectable<_NestedValueSender, __receiver>) : __base_t{static_cast<_NestedValueReceiver&&>(__rcvr)} - , __nested_value_op_{stdexec::connect(static_cast<_NestedValueSender&&>(__result), __receiver{this, __op})} - , __op_{__op} {} + , __nested_value_op_{stdexec::connect( + static_cast<_NestedValueSender&&>(__result), + __receiver{this, __op})} + , __op_{__op} { + } void start() & noexcept { __op_->nested_value_started(); @@ -554,7 +570,8 @@ namespace exec { using sender_concept = stdexec::sender_t; template - using __nested_value_op_t = stdexec::__t<__nested_value_op<_NestedValueSender, _NestedValueReceiverId, _ErrorStorage>>; + using __nested_value_op_t = + stdexec::__t<__nested_value_op<_NestedValueSender, _NestedValueReceiverId, _ErrorStorage>>; template using __receiver = __receive_nested_value<_NestedValueReceiverId, _ErrorStorage>; @@ -564,25 +581,26 @@ namespace exec { template _Self, class... _Env> static auto get_completion_signatures(_Self&&, _Env&&...) noexcept -> stdexec::transform_completion_signatures< - stdexec::completion_signatures_of_t<_NestedValueSender, _Env...>, - stdexec::completion_signatures, - stdexec::__sigs::__default_set_value, - drop> { + stdexec::completion_signatures_of_t<_NestedValueSender, _Env...>, + stdexec::completion_signatures, + stdexec::__sigs::__default_set_value, + drop + > { return {}; } template _Self, receiver _NestedValueReceiver> static auto connect(_Self&& __self, _NestedValueReceiver&& __rcvr) - noexcept( - __nothrow_constructible_from< - __nested_value_op_t>, - _NestedValueReceiver, - _NestedValueSender, - __operation_base_interface_t*>) - -> __nested_value_op_t> { - return {static_cast<_NestedValueReceiver&&>(__rcvr), - static_cast<_NestedValueSender&&>(__self.__nested_value_), - __self.__op_}; + noexcept(__nothrow_constructible_from< + __nested_value_op_t>, + _NestedValueReceiver, + _NestedValueSender, + __operation_base_interface_t* + >) -> __nested_value_op_t> { + return { + static_cast<_NestedValueReceiver&&>(__rcvr), + static_cast<_NestedValueSender&&>(__self.__nested_value_), + __self.__op_}; } }; }; @@ -600,7 +618,8 @@ namespace exec { // struct __next_operation_interface { - virtual ~__next_operation_interface() {} + virtual ~__next_operation_interface() { + } virtual void nested_sequence_complete() noexcept = 0; virtual void nested_sequence_break() noexcept = 0; }; @@ -614,9 +633,9 @@ namespace exec { __next_operation_base(_NextReceiver __receiver, _OperationBase* __op) noexcept(__nothrow_move_constructible<_NextReceiver>) : __next_operation_interface{} - , __receiver_ {static_cast<_NextReceiver&&>(__receiver)} + , __receiver_{static_cast<_NextReceiver&&>(__receiver)} , __op_{__op} { - } + } void nested_sequence_complete() noexcept override { auto __op = __op_; @@ -652,20 +671,19 @@ namespace exec { using __error_storage_t = typename _OperationBase::__error_storage_t; template - using __nested_value_sender_t = stdexec::__t<__nested_value_sender<_NestedValue, __error_storage_t>>; + using __nested_value_sender_t = + stdexec::__t<__nested_value_sender<_NestedValue, __error_storage_t>>; template - auto set_next(_NestedValue&& __nested_value) - noexcept( - __nothrow_callable< - exec::set_next_t, - decltype(__op_->__receiver_), - __nested_value_sender_t<_NestedValue>>) - -> next_sender auto { - return exec::set_next(__op_->__receiver_, - __nested_value_sender_t<_NestedValue>{ - static_cast<_NestedValue&&>(__nested_value), - __op_}); + auto set_next(_NestedValue&& __nested_value) noexcept(__nothrow_callable< + exec::set_next_t, + decltype(__op_->__receiver_), + __nested_value_sender_t<_NestedValue> + >) -> next_sender auto { + return exec::set_next( + __op_->__receiver_, + __nested_value_sender_t<_NestedValue>{ + static_cast<_NestedValue&&>(__nested_value), __op_}); } void set_value() noexcept { @@ -688,11 +706,11 @@ namespace exec { } }; - struct _INVALID_ARGUMENT_TO_MERGE_WITH_REQUIRES_A_SEQUENCE_OF_SEQUENCES_ {}; + struct _INVALID_ARGUMENT_TO_MERGE_WITH_REQUIRES_A_SEQUENCE_OF_SEQUENCES_ { }; template struct __value_completions_error { - template + template using __f = __mexception< _INVALID_ARGUMENT_TO_MERGE_WITH_REQUIRES_A_SEQUENCE_OF_SEQUENCES_, _WITH_SEQUENCE_<_Sequence>, @@ -711,60 +729,64 @@ namespace exec { template struct __arg_of_t { - template - static constexpr bool __valid_args = sizeof...(_Args) == 1 && (has_sequence_item_types<_Args, _Env...> && ... && true); + template + static constexpr bool __valid_args = sizeof...(_Args) == 1 + && (has_sequence_item_types<_Args, _Env...> && ... + && true); - template + template using __checked_eval_t = stdexec::__if_c< - __valid_args<_Args...>, - stdexec::__types<_Args...>, - __value_completions_error<_Sequence, _Sender, _Env...>>; + __valid_args<_Args...>, + stdexec::__types<_Args...>, + __value_completions_error<_Sequence, _Sender, _Env...> + >; - template + template using __f = __checked_eval_t<_Args...>; }; template struct __gather_sequences_t { - template - using __f = - stdexec::__gather_completion_signatures< - stdexec::completion_signatures_of_t<_Sender, _Env...>, - stdexec::set_value_t, - // if set_value - __arg_of_t<_Sequence, _Sender, _Env...>::template __f, - // else remove - stdexec::__mconst>::__f, - // concat to __types result - stdexec::__mtry_q< - stdexec::__mconcat>::template __f> - ::__f - >; - }; + template + using __f = stdexec::__gather_completion_signatures< + stdexec::completion_signatures_of_t<_Sender, _Env...>, + stdexec::set_value_t, + // if set_value + __arg_of_t<_Sequence, _Sender, _Env...>::template __f, + // else remove + stdexec::__mconst>::__f, + // concat to __types result + stdexec::__mtry_q>::template __f>::__f + >; + }; template - using __nested_sequences_from_item_type_t = - stdexec::__mapply< - stdexec::__if_c< - stdexec::__mvalid + using __nested_sequences_from_item_type_t = stdexec::__mapply< + stdexec::__if_c< + stdexec::__mvalid && stdexec::__mvalid<__gather_sequences_t<_Sequence, _Sender, _Env...>::template __f>, - __gather_sequences_t<_Sequence, _Sender, _Env...>, - __value_completions_error<_Sequence, _Sender, _Env...>>, - stdexec::__completion_signatures_of_t<_Sender, _Env...>>; + __gather_sequences_t<_Sequence, _Sender, _Env...>, + __value_completions_error<_Sequence, _Sender, _Env...> + >, + stdexec::__completion_signatures_of_t<_Sender, _Env...> + >; template struct __nested_sequences_t { template using __f = stdexec::__mapply< - stdexec::__munique>, + stdexec::__munique>, stdexec::__minvoke< stdexec::__mconcat>, - __nested_sequences_from_item_type_t<_Sequence, _Senders, _Env...>...>>; + __nested_sequences_from_item_type_t<_Sequence, _Senders, _Env...>... + > + >; }; template - using __nested_sequences = __mapply<__nested_sequences_t<_Sequence, _Env...>, __item_types_of_t<_Sequence, _Env...>>; + using __nested_sequences = + __mapply<__nested_sequences_t<_Sequence, _Env...>, __item_types_of_t<_Sequence, _Env...>>; // // __all_nested_values extracts the types of all the nested value senders. @@ -775,29 +797,33 @@ namespace exec { template using __f = stdexec::__minvoke< - stdexec::__mconcat>, - __item_types_of_t<_Sequences, _Env...>...>; + stdexec::__mconcat>, + __item_types_of_t<_Sequences, _Env...>... + >; }; template - using __all_nested_values = __mapply<__all_nested_values_t<_Env...>, __nested_sequences<_Sequence, _Env...>>; + using __all_nested_values = + __mapply<__all_nested_values_t<_Env...>, __nested_sequences<_Sequence, _Env...>>; // // __error_types extracts the types of all the errors emitted by all the senders in the list. // - template> + template > struct __error_types_t { - template + template using __f = stdexec::error_types_of_t<_Sender, _Env, stdexec::__types>; }; - template + template using __error_types = stdexec::__mapply< - stdexec::__mtransform< - __error_types_t<_Env...>, - stdexec::__mconcat>>, - _Senders>; + stdexec::__mtransform< + __error_types_t<_Env...>, + stdexec::__mconcat> + >, + _Senders + >; // // __errors extracts the types of all the errors emitted by: @@ -808,18 +834,18 @@ namespace exec { // output sequence. // - template + template using __errors = stdexec::__minvoke< - stdexec::__mconcat>, - // always include std::exception_ptr - stdexec::__types, - // include errors from senders of the nested sequences - __error_types<__item_types_of_t<_Sequence, _Env...>, _Env...>, - // include errors from the nested sequences - __error_types<__merge_each::__compute::__nested_sequences<_Sequence, _Env...>, _Env...>, - // include errors from all the item type senders of all the nested sequences - __error_types<__merge_each::__compute::__all_nested_values<_Sequence, _Env...>, _Env...> - >; + stdexec::__mconcat>, + // always include std::exception_ptr + stdexec::__types, + // include errors from senders of the nested sequences + __error_types<__item_types_of_t<_Sequence, _Env...>, _Env...>, + // include errors from the nested sequences + __error_types<__merge_each::__compute::__nested_sequences<_Sequence, _Env...>, _Env...>, + // include errors from all the item type senders of all the nested sequences + __error_types<__merge_each::__compute::__all_nested_values<_Sequence, _Env...>, _Env...> + >; // // __error_variant makes a variant type of all the errors @@ -828,18 +854,18 @@ namespace exec { // all active operations are completed. // - template - using __error_variant = stdexec::__mapply< - __q, - __errors<_Sequence, _Env...>>; + template + using __error_variant = + stdexec::__mapply<__q, __errors<_Sequence, _Env...>>; // // __nested_values extracts the types of all the nested value senders and // builds the item_types list for the merge_each sequence sender. // - template - using __nested_value_sender_t = stdexec::__t<__nested_value_sender<_NestedValueSender, _ErrorStorage>>; + template + using __nested_value_sender_t = + stdexec::__t<__nested_value_sender<_NestedValueSender, _ErrorStorage>>; template struct __nested_values_t { @@ -847,38 +873,47 @@ namespace exec { template using __f = stdexec::__mapply< stdexec::__munique>, - stdexec::__types< - __nested_value_sender_t<_AllItems, _ErrorStorage>..., - __t<__error_sender<_ErrorStorage>>>>; + stdexec::__types< + __nested_value_sender_t<_AllItems, _ErrorStorage>..., + __t<__error_sender<_ErrorStorage>> + > + >; }; template using __nested_values = stdexec::__mapply< __nested_values_t<__error_variant<_Sequence, _Env...>, _Env...>, - __all_nested_values<_Sequence, _Env...>>; + __all_nested_values<_Sequence, _Env...> + >; // // __nested_sequence_ops_variant makes a variant that contains the // types of all the nested sequence operations. // - template + template struct __nested_sequence_op_t { - template + template using __f = subscribe_result_t<_Sequence, __receive_nested_values<_OperationBase>>; }; - template - using __operation_base_t = __operation_base<_Receiver, __error_variant<_Sequence, __env_with_inplace_stop_token_result_t>>>; + template + using __operation_base_t = __operation_base< + _Receiver, + __error_variant<_Sequence, __env_with_inplace_stop_token_result_t>> + >; - template + template using __nested_sequence_ops_variant = stdexec::__mapply< stdexec::__mtransform< __nested_sequence_op_t<__operation_base_t<_Sequence, _Receiver>>, - stdexec::__qq>, - __merge_each::__compute::__nested_sequences<_Sequence, __env_with_inplace_stop_token_result_t>> + stdexec::__qq + >, + __merge_each::__compute::__nested_sequences< + _Sequence, + __env_with_inplace_stop_token_result_t> + > >; - }; // @@ -902,25 +937,30 @@ namespace exec { template auto set_value(_NestedSequence&& __sequence) noexcept { - using __nested_op_t = subscribe_result_t<_NestedSequence, __receive_nested_values<_OperationBase>>; + using __nested_op_t = + subscribe_result_t<_NestedSequence, __receive_nested_values<_OperationBase>>; if constexpr ( __nothrow_subscribable<_NestedSequence, __receive_nested_values<_OperationBase>> && stdexec::__nothrow_constructible_from<_NestedSeqOp, __nested_op_t>) { auto& __nested_seq_op = __next_seq_op_->__nested_seq_op_.emplace_from( - [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { - return subscribe(static_cast<_NestedSequence&&>(__sequence), static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); - }, - static_cast<_NestedSequence&&>(__sequence), - __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); + [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { + return subscribe( + static_cast<_NestedSequence&&>(__sequence), + static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); + }, + static_cast<_NestedSequence&&>(__sequence), + __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); stdexec::start(__nested_seq_op); } else { STDEXEC_TRY { auto& __nested_seq_op = __next_seq_op_->__nested_seq_op_.emplace_from( - [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { - return subscribe(static_cast<_NestedSequence&&>(__sequence), static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); - }, - static_cast<_NestedSequence&&>(__sequence), - __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); + [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { + return subscribe( + static_cast<_NestedSequence&&>(__sequence), + static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); + }, + static_cast<_NestedSequence&&>(__sequence), + __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); stdexec::start(__nested_seq_op); } STDEXEC_CATCH_ALL { @@ -946,7 +986,12 @@ namespace exec { } }; - template + template < + class _NestedSequenceSender, + class _NextReceiverId, + class _OperationBase, + class _NestedSeqOp + > struct __next_sequence_op { using _NextReceiver = stdexec::__t<_NextReceiverId>; using __base_t = __next_operation_base<_NextReceiver, _OperationBase, _NestedSeqOp>; @@ -964,7 +1009,10 @@ namespace exec { && __nothrow_connectable<_NestedSequenceSender, __receiver>) : __base_t{static_cast<_NextReceiver&&>(__rcvr), __op} , __op_(__op) - , __nested_sequence_op_{stdexec::connect(static_cast<_NestedSequenceSender&&>(__nested_sequence), __receiver{this, __op_})} {} + , __nested_sequence_op_{stdexec::connect( + static_cast<_NestedSequenceSender&&>(__nested_sequence), + __receiver{this, __op_})} { + } void start() & noexcept { __op_->nested_sequence_started(); @@ -981,7 +1029,9 @@ namespace exec { using sender_concept = stdexec::sender_t; template - using __next_sequence_op_t = stdexec::__t<__next_sequence_op<_NestedSequenceSender, _NextReceiverId, _OperationBase, _NestedSeqOp>>; + using __next_sequence_op_t = stdexec::__t< + __next_sequence_op<_NestedSequenceSender, _NextReceiverId, _OperationBase, _NestedSeqOp> + >; _OperationBase* __op_; _NestedSequenceSender __nested_sequence_; @@ -994,16 +1044,16 @@ namespace exec { template _Self, receiver _NextReceiver> static auto connect(_Self&& __self, _NextReceiver&& __rcvr) - noexcept( - __nothrow_constructible_from< - __next_sequence_op_t>, - _NextReceiver, - _OperationBase*, - _NestedSequenceSender>) - -> __next_sequence_op_t> { - return {static_cast<_NextReceiver&&>(__rcvr), - __self.__op_, - static_cast<_NestedSequenceSender&&>(__self.__nested_sequence_)}; + noexcept(__nothrow_constructible_from< + __next_sequence_op_t>, + _NextReceiver, + _OperationBase*, + _NestedSequenceSender + >) -> __next_sequence_op_t> { + return { + static_cast<_NextReceiver&&>(__rcvr), + __self.__op_, + static_cast<_NestedSequenceSender&&>(__self.__nested_sequence_)}; } }; }; @@ -1021,19 +1071,20 @@ namespace exec { using receiver_concept = receiver_t; template - using __next_sequence_sender_t = stdexec::__t<__next_sequence_sender<_NestedSequenceSender, _OperationBase, _NestedSeqOp>>; + using __next_sequence_sender_t = + stdexec::__t<__next_sequence_sender<_NestedSequenceSender, _OperationBase, _NestedSeqOp>>; _OperationBase* __op_; template auto set_next(_NestedSequenceSender&& __nested_sequence) - noexcept( - __nothrow_constructible_from< - __next_sequence_sender_t<_NestedSequenceSender>, - _OperationBase*, - _NestedSequenceSender>) - -> next_sender auto { - return __next_sequence_sender_t<_NestedSequenceSender>{__op_, static_cast<_NestedSequenceSender>(__nested_sequence)}; + noexcept(__nothrow_constructible_from< + __next_sequence_sender_t<_NestedSequenceSender>, + _OperationBase*, + _NestedSequenceSender + >) -> next_sender auto { + return __next_sequence_sender_t<_NestedSequenceSender>{ + __op_, static_cast<_NestedSequenceSender>(__nested_sequence)}; } void set_value() noexcept { @@ -1060,7 +1111,10 @@ namespace exec { template struct __operation { using _Receiver = stdexec::__t<_ReceiverId>; - using __error_storage_t = __compute::__error_variant<_Sequence, __env_with_inplace_stop_token_result_t>>; + using __error_storage_t = __compute::__error_variant< + _Sequence, + __env_with_inplace_stop_token_result_t> + >; using __base_t = __operation_base<_Receiver, __error_storage_t>; struct __t : __base_t { using __id = __operation; @@ -1072,12 +1126,11 @@ namespace exec { using __op_t = subscribe_result_t<_Sequence, __receiver>; __op_t __op_; - __t(_Receiver __rcvr, _Sequence __sequence) - noexcept( - __nothrow_subscribable<_Sequence, __receiver> - && __nothrow_move_constructible<_Receiver>) + __t(_Receiver __rcvr, _Sequence __sequence) noexcept( + __nothrow_subscribable<_Sequence, __receiver> && __nothrow_move_constructible<_Receiver>) : __base_t{static_cast<_Receiver&&>(__rcvr)} - ,__op_{subscribe(static_cast<_Sequence&&>(__sequence), __receiver{this})} {} + , __op_{subscribe(static_cast<_Sequence&&>(__sequence), __receiver{this})} { + } void start() & noexcept { this->__nested_stop_.register_token(this->__receiver_); @@ -1099,15 +1152,12 @@ namespace exec { template auto operator()(__ignore, __ignore, _Sequence __sequence) - noexcept( - __nothrow_constructible_from< - __t<__operation<__id<_Receiver>, _Sequence>>, - _Receiver, - _Sequence>) - -> __t<__operation<__id<_Receiver>, _Sequence>> { - return { - static_cast<_Receiver&&>(__rcvr_), - static_cast<_Sequence&&>(__sequence)}; + noexcept(__nothrow_constructible_from< + __t<__operation<__id<_Receiver>, _Sequence>>, + _Receiver, + _Sequence + >) -> __t<__operation<__id<_Receiver>, _Sequence>> { + return {static_cast<_Receiver&&>(__rcvr_), static_cast<_Sequence&&>(__sequence)}; } }; @@ -1139,19 +1189,18 @@ namespace exec { struct merge_each_t { template - auto operator()(_Sequence&& __sequence) const - noexcept(__nothrow_decay_copyable<_Sequence>) + auto operator()(_Sequence&& __sequence) const noexcept(__nothrow_decay_copyable<_Sequence>) -> __well_formed_sequence_sender auto { - return make_sequence_expr( - __(), static_cast<_Sequence&&>(__sequence)); + return make_sequence_expr(__(), static_cast<_Sequence&&>(__sequence)); } template _Self, class... _Env> static auto get_item_types(_Self&&, _Env&&...) noexcept { - return __minvoke< - __mtry_catch<__q<__compute::__nested_values>, __q<__argument_error_t>>, - __child_of<_Self>, - __env_with_inplace_stop_token_result_t<_Env...>>(); + return __minvoke< + __mtry_catch<__q<__compute::__nested_values>, __q<__argument_error_t>>, + __child_of<_Self>, + __env_with_inplace_stop_token_result_t<_Env...> + >(); } template @@ -1167,24 +1216,27 @@ namespace exec { }; template - using __completions = __mapply<__completions_t<_Self, _Env...>, __compute::__nested_sequences<__child_of<_Self>, _Env...>>; + using __completions = __mapply< + __completions_t<_Self, _Env...>, + __compute::__nested_sequences<__child_of<_Self>, _Env...> + >; template _Self, class... _Env> static auto get_completion_signatures(_Self&&, _Env&&...) noexcept { - return __minvoke<__mtry_catch<__q<__completions>, __q<__argument_error_t>>, - _Self, - __env_with_inplace_stop_token_result_t<_Env...>>{}; + return __minvoke< + __mtry_catch<__q<__completions>, __q<__argument_error_t>>, + _Self, + __env_with_inplace_stop_token_result_t<_Env...> + >{}; } static constexpr auto subscribe = [](_Sequence&& __sndr, _Receiver __rcvr) noexcept( __nothrow_callable<__sexpr_apply_t, _Sequence, __subscribe_fn<_Receiver>>) - -> __sexpr_apply_result_t<_Sequence, __subscribe_fn<_Receiver>> - { + -> __sexpr_apply_result_t<_Sequence, __subscribe_fn<_Receiver>> { static_assert(sender_expr_for<_Sequence, merge_each_t>); return __sexpr_apply(static_cast<_Sequence&&>(__sndr), __subscribe_fn<_Receiver>{__rcvr}); }; - }; } // namespace __merge_each diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 3ee9ae3f2..69ad826b2 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -28,7 +28,7 @@ namespace exec { template struct _WITH_SEQUENCES_; - } + } // namespace __errs template using _WITH_SEQUENCE_ = __errs::_WITH_SEQUENCE_>; @@ -56,11 +56,12 @@ namespace exec { // This concept checks if a given sender satisfies the requirements to be returned from `set_next`. template > - concept next_sender = stdexec::sender_in<_Sender, _Env> - && __sequence_sndr::__all_contained_in< - stdexec::completion_signatures_of_t<_Sender, _Env>, - stdexec::completion_signatures - >; + concept next_sender = + stdexec::sender_in<_Sender, _Env> + && __sequence_sndr::__all_contained_in< + stdexec::completion_signatures_of_t<_Sender, _Env>, + stdexec::completion_signatures + >; namespace __sequence_sndr { @@ -277,12 +278,11 @@ namespace exec { template concept has_sequence_item_types = - stdexec::sender_in<_Sequence, _Env...> - && requires(_Sequence&& __sequence, _Env&&... __env) { - { get_item_types(static_cast<_Sequence &&>(__sequence), static_cast<_Env &&>(__env)...) }; + stdexec::sender_in<_Sequence, _Env...> && requires(_Sequence&& __sequence, _Env&&... __env) { + { get_item_types(static_cast<_Sequence&&>(__sequence), static_cast<_Env&&>(__env)...) }; }; - template + template concept sequence_sender = stdexec::sender_in<_Sequence, _Env...> && enable_sequence_sender>; @@ -321,8 +321,8 @@ namespace exec { template concept __well_formed_item_types = requires(stdexec::__decay_t<_ItemTypes>* __item_types) { - { exec::__check_items<_Sequence>(__item_types) } -> stdexec::__ok; - }; + { exec::__check_items<_Sequence>(__item_types) } -> stdexec::__ok; + }; template requires stdexec::__merror<_Sequence> @@ -344,8 +344,8 @@ namespace exec { template concept __well_formed_item_senders = requires(stdexec::__decay_t<_Sequence>* __sequence) { - { exec::__check_sequence(__sequence) } -> stdexec::__ok; - }; + { exec::__check_sequence(__sequence) } -> stdexec::__ok; + }; template concept __well_formed_sequence_sender = stdexec::__well_formed_sender<_Sequence> @@ -353,37 +353,39 @@ namespace exec { && __well_formed_item_senders<_Sequence>; template - concept sequence_sender_in = sequence_sender<_Sequence, _Env...> - && requires(_Sequence&& __sequence, _Env&&... __env) { - { get_item_types(static_cast<_Sequence &&>(__sequence), static_cast<_Env &&>(__env)...) } - -> __well_formed_item_types<_Sequence>; - }; + concept sequence_sender_in = + sequence_sender<_Sequence, _Env...> && requires(_Sequence&& __sequence, _Env&&... __env) { + { + get_item_types(static_cast<_Sequence&&>(__sequence), static_cast<_Env&&>(__env)...) + } -> __well_formed_item_types<_Sequence>; + }; namespace __debug { - template , class _Sequence> - void __debug_sequence_sender(_Sequence&& __sequence, const _Env& = {}); + template , class _Sequence> + void __debug_sequence_sender(_Sequence&& __sequence, const _Env& = {}); } // namespace __debug using __debug::__debug_sequence_sender; template static constexpr auto __diagnose_sequence_sender_concept_failure(); - #if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() +#if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() // __checked_completion_signatures is for catching logic bugs in a sender's metadata. If sender // and sender_in are both true, then they had better report the same metadata. This // completion signatures wrapper enforces that at compile time. template - auto __checked_item_types(_Sequence && __sequence, _Env &&... __env) noexcept { + auto __checked_item_types(_Sequence&& __sequence, _Env&&... __env) noexcept { using __item_types_t = __item_types_of_t<_Sequence, _Env...>; static_assert(stdexec::__ok<__item_types_t>, "get_item_types returned an error"); - exec::__debug_sequence_sender(static_cast<_Sequence &&>(__sequence), __env...); + exec::__debug_sequence_sender(static_cast<_Sequence&&>(__sequence), __env...); return __item_types_t{}; } template requires sequence_sender<_Sequence, _Env...> - using item_types_of_t = - decltype(exec::__checked_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); + using item_types_of_t = decltype(exec::__checked_item_types( + stdexec::__declval<_Sequence>(), + stdexec::__declval<_Env>()...)); #else template requires sequence_sender_in<_Sequence, _Env...> @@ -495,17 +497,20 @@ namespace exec { && sender_to, __stopped_means_break_t<_Receiver>>; template - using __subscribe_member_result_t = decltype(__declval<_Sequence>().subscribe(__declval<_Receiver>())); + using __subscribe_member_result_t = decltype(__declval<_Sequence>() + .subscribe(__declval<_Receiver>())); template using __subscribe_static_member_result_t = decltype(STDEXEC_REMOVE_REFERENCE( _Sequence)::subscribe(__declval<_Sequence>(), __declval<_Receiver>())); template - concept __subscribable_with_member = __mvalid<__subscribe_member_result_t, _Sequence, _Receiver>; + concept __subscribable_with_member = + __mvalid<__subscribe_member_result_t, _Sequence, _Receiver>; template - concept __subscribable_with_static_member = __mvalid<__subscribe_static_member_result_t, _Sequence, _Receiver>; + concept __subscribable_with_static_member = + __mvalid<__subscribe_static_member_result_t, _Sequence, _Receiver>; template concept __subscribable_with_tag_invoke = tag_invocable; @@ -538,7 +543,9 @@ namespace exec { __nothrow_callable>; using __tfx_sequence_t = __tfx_sequence_t<_Sequence, _Receiver>; - static_assert(sequence_sender<_Sequence> || has_sequence_item_types<_Sequence, env_of_t<_Receiver>>, "The first argument to stdexec::subscribe must be a sequence sender"); + static_assert( + sequence_sender<_Sequence> || has_sequence_item_types<_Sequence, env_of_t<_Receiver>>, + "The first argument to stdexec::subscribe must be a sequence sender"); static_assert( receiver<_Receiver>, "The second argument to stdexec::subscribe must be a receiver"); #if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() @@ -636,12 +643,11 @@ namespace exec { // This should generate an instantiate backtrace that contains useful // debugging information. - auto&& __tfx_sequence = stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); - return __tfx_sequence - .subscribe( - static_cast<__tfx_sequence_t&&>(__tfx_sequence), - static_cast<_Receiver&&>(__rcvr)); - } else { + auto&& __tfx_sequence = + stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); + return __tfx_sequence.subscribe( + static_cast<__tfx_sequence_t&&>(__tfx_sequence), static_cast<_Receiver&&>(__rcvr)); + } else { // sender as sequence of one fallback // This should generate an instantiate backtrace that contains useful @@ -705,7 +711,7 @@ namespace exec { } //////////////////////////////////////////////////////////////////////////////// -#define STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE \ +#define STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE \ "\n" \ "\n" \ "The given type is not a sequence sender because stdexec::enable_sequence_sender\n" \ @@ -735,43 +741,46 @@ namespace exec { " inline constexpr bool stdexec::enable_sequence_sender = true;\n" //////////////////////////////////////////////////////////////////////////////// -# define STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR \ - "\n" \ - "\n" \ - "Trying to compute the sequences's item types resulted in an error. See\n" \ - "the rest of the compiler diagnostic for clues. Look for the string \"_ERROR_\".\n" +#define STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR \ + "\n" \ + "\n" \ + "Trying to compute the sequences's item types resulted in an error. See\n" \ + "the rest of the compiler diagnostic for clues. Look for the string \"_ERROR_\".\n" //////////////////////////////////////////////////////////////////////////////// -# define STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE \ - "\n" \ - "\n" \ - "The member function `get_item_types` of the sequence returned an\n" \ - "invalid type.\n" \ - "\n" \ - "A sender's `get_item_types` function must return a specialization of\n" \ - "`exec::item_types<...>`, as follows:\n" \ - "\n" \ - " class MySequence\n" \ - " {\n" \ - " public:\n" \ - " using sender_concept = exec::sequence_sender_t;\n" \ - "\n" \ - " template \n" \ - " auto get_item_types(_Env&&...) -> exec::item_types<\n" \ - " // This sequence produces void items...\n" \ - " stdexec::__call_result_t>\n" \ - " {\n" \ - " return {};\n" \ - " }\n" \ - " ...\n" \ - " };\n" +#define STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE \ + "\n" \ + "\n" \ + "The member function `get_item_types` of the sequence returned an\n" \ + "invalid type.\n" \ + "\n" \ + "A sender's `get_item_types` function must return a specialization of\n" \ + "`exec::item_types<...>`, as follows:\n" \ + "\n" \ + " class MySequence\n" \ + " {\n" \ + " public:\n" \ + " using sender_concept = exec::sequence_sender_t;\n" \ + "\n" \ + " template \n" \ + " auto get_item_types(_Env&&...) -> exec::item_types<\n" \ + " // This sequence produces void items...\n" \ + " stdexec::__call_result_t>\n" \ + " {\n" \ + " return {};\n" \ + " }\n" \ + " ...\n" \ + " };\n" // Used to report a meaningful error message when the sender_in // concept check fails. template static constexpr auto __diagnose_sequence_sender_concept_failure() { - if constexpr (!enable_sequence_sender> && !has_sequence_item_types, _Env...>) { - static_assert(enable_sequence_sender<_Sequence>, STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE); + if constexpr ( + !enable_sequence_sender> + && !has_sequence_item_types, _Env...>) { + static_assert( + enable_sequence_sender<_Sequence>, STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE); } else if constexpr (!stdexec::__detail::__consistent_completion_domains<_Sequence>) { static_assert( stdexec::__detail::__consistent_completion_domains<_Sequence>, @@ -801,8 +810,8 @@ namespace exec { static_assert( __well_formed_item_senders<_Sequence>, STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE); - //} else { - // stdexec::__diagnose_sender_concept_failure<_Sequence, _Env...>(); + //} else { + // stdexec::__diagnose_sender_concept_failure<_Sequence, _Env...>(); } #if STDEXEC_MSVC() || STDEXEC_NVHPC() // MSVC and NVHPC need more encouragement to print the type of the diff --git a/test/exec/sequence/test_merge_each.cpp b/test/exec/sequence/test_merge_each.cpp index 188da5408..ea1f44d02 100644 --- a/test/exec/sequence/test_merge_each.cpp +++ b/test/exec/sequence/test_merge_each.cpp @@ -33,9 +33,9 @@ #include #include -# include -# include -# include +#include +#include +#include namespace { using namespace std::chrono_literals; @@ -44,21 +44,21 @@ namespace { template concept __equivalent = __sequence_sndr::__all_contained_in<_A, _B> - && __sequence_sndr::__all_contained_in<_B, _A> - && ex::__v> - == ex::__v>; + && __sequence_sndr::__all_contained_in<_B, _A> + && ex::__v> + == ex::__v>; struct null_receiver { using __id = null_receiver; using __t = null_receiver; using receiver_concept = ex::receiver_t; - template + template void set_value(_Values&&...) noexcept { } template - void set_error(_Error&& ) noexcept { + void set_error(_Error&&) noexcept { } void set_stopped() noexcept { @@ -70,106 +70,120 @@ namespace { } struct ignore_values_fn_t { - template - void operator()(_Vs&&...) const noexcept {} + template + void operator()(_Vs&&...) const noexcept { + } }; template [[nodiscard]] - auto set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) - -> next_sender auto { - return stdexec::upon_error(stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); + auto + set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) -> next_sender auto { + return stdexec::upon_error( + stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); } }; // a sequence adaptor that applies a function to each item - [[maybe_unused]] static constexpr auto then_each = [](auto f) { + [[maybe_unused]] + static constexpr auto then_each = [](auto f) { return exec::transform_each(ex::then(f)); }; // a sequence adaptor that schedules each item to complete // on the specified scheduler - [[maybe_unused]] static constexpr auto continues_each_on = [](auto sched) { + [[maybe_unused]] + static constexpr auto continues_each_on = [](auto sched) { return exec::transform_each(ex::continues_on(sched)); }; // a sequence adaptor that schedules each item to complete // on the specified scheduler after the specified duration - [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { - auto delay_value = [](Value&& value, Sched sched, duration_of_t after){ + [[maybe_unused]] + static constexpr auto delays_each_on = + [](auto sched, duration_of_t after) noexcept { + auto delay_value = [](Value&& value, Sched sched, duration_of_t after) { return sequence(schedule_after(sched, after), static_cast(value)); }; - auto delay_adaptor = stdexec::__binder_back>{{sched, after}, {}, {}}; - return exec::transform_each(delay_adaptor); - }; + auto delay_adaptor = + stdexec::__binder_back>{ + {sched, after}, + {}, + {} + }; + return exec::transform_each(delay_adaptor); + }; // a sequence adaptor that applies a function to each item // the function must produce a sequence // all the sequences returned from the function are merged - [[maybe_unused]] static constexpr auto flat_map = [](auto&& f) { + [[maybe_unused]] + static constexpr auto flat_map = [](auto&& f) { auto map_merge = [](auto&& sequence, auto&& f) noexcept { - return merge_each(exec::transform_each( - static_cast(sequence), - ex::then(static_cast(f)))); - }; - return stdexec::__binder_back{{static_cast(f)}, {}, {}}; + return merge_each( + exec::transform_each( + static_cast(sequence), ex::then(static_cast(f)))); + }; + return stdexec::__binder_back{ + {static_cast(f)}, {}, {}}; }; // when_all requires a successful completion // however stop_after_on has no successful completion // this uses variant_sender to add a successful completion // (the successful completion will never occur) - [[maybe_unused]] static constexpr auto with_void = [](auto&& sender) noexcept - -> variant_sender< - stdexec::__call_result_t, - decltype(sender)> { + [[maybe_unused]] + static constexpr auto with_void = [](auto&& sender) noexcept + -> variant_sender, decltype(sender)> { return {static_cast(sender)}; }; // with_stop_token_from adds get_stop_token query, that returns the // token for the provided stop_source, to the receiver env - [[maybe_unused]] static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { + [[maybe_unused]] + static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { return ex::write_env(ex::prop{ex::get_stop_token, stop_source.get_token()}); }; // log_start completes with the provided sequence after printing provided string - [[maybe_unused]] auto log_start = [](auto sequence, auto message) { + [[maybe_unused]] + auto log_start = [](auto sequence, auto message) { return exec::sequence( - ex::read_env(ex::get_stop_token) - | stdexec::then([message](auto&& token) noexcept { - UNSCOPED_INFO(message - << (token.stop_requested() ? ", stop was requested" : ", stop not requested") - << ", on thread id: " << std::this_thread::get_id()); - }), - ex::just(sequence)); + ex::read_env(ex::get_stop_token) | stdexec::then([message](auto&& token) noexcept { + UNSCOPED_INFO( + message << (token.stop_requested() ? ", stop was requested" : ", stop not requested") + << ", on thread id: " << std::this_thread::get_id()); + }), + ex::just(sequence)); }; // log_sequence prints the message when each value in the sequence is emitted - [[maybe_unused]] auto log_sequence = [](auto sequence, auto message) { - return sequence - | then_each([message](auto&& value) mutable noexcept { - UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); - return value; - }); + [[maybe_unused]] + auto log_sequence = [](auto sequence, auto message) { + return sequence | then_each([message](auto&& value) mutable noexcept { + UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); + return value; + }); }; // emits_stopped completes with set_stopped after printing info - [[maybe_unused]] auto emits_stopped = []() { - return ex::just() - | stdexec::let_value([]() noexcept { - UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); - return ex::just_stopped(); - }); + [[maybe_unused]] + auto emits_stopped = []() { + return ex::just() | stdexec::let_value([]() noexcept { + UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); }; // emits_error completes with set_error(error) after printing info - [[maybe_unused]] auto emits_error = [](auto error) { - return ex::just() - | stdexec::let_value([error]() noexcept { - UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); + [[maybe_unused]] + auto emits_error = [](auto error) { + return ex::just() | stdexec::let_value([error]() noexcept { + UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); }; #if STDEXEC_HAS_STD_RANGES() // a sequence of numbers from itoa() - [[maybe_unused]] static constexpr auto range = [](auto from, auto to) { + [[maybe_unused]] + static constexpr auto range = [](auto from, auto to) { return exec::iterate(std::views::iota(from, to)); }; - template + template struct as_sequence_t : Sender { using sender_concept = sequence_sender_t; using item_types = exec::item_types; @@ -183,36 +197,33 @@ namespace { "[sequence_senders][merge_each][empty_sequence]") { using empty_sequence_t = stdexec::__call_result_t; - [[maybe_unused]] std::array array{ - empty_sequence(), - empty_sequence()}; + [[maybe_unused]] + std::array array{empty_sequence(), empty_sequence()}; - [[maybe_unused]] auto sequences = iterate(std::views::all(array)); + [[maybe_unused]] + auto sequences = iterate(std::views::all(array)); using sequences_t = decltype(sequences); - STATIC_REQUIRE(ex::__ok< - item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - stdexec::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>); - [[maybe_unused]] auto merged = merge_each(sequences); + [[maybe_unused]] + auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok< - item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - stdexec::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>); STATIC_REQUIRE(ex::__callable); int count = 0; - auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ - ++count; - UNSCOPED_INFO("item: " << x - << ", on thread id: " << std::this_thread::get_id()); - }))); + auto v = ex::sync_wait(ignore_all_values( + merged | then_each([&count](int x) { + ++count; + UNSCOPED_INFO("item: " << x << ", on thread id: " << std::this_thread::get_id()); + }))); CHECK(count == 0); CHECK(v.has_value() == true); @@ -224,35 +235,32 @@ namespace { using range_sender_t = stdexec::__call_result_t; - [[maybe_unused]] std::array array{ - range(100,120), - range(200,220)}; + [[maybe_unused]] + std::array array{range(100, 120), range(200, 220)}; - [[maybe_unused]] auto sequences = iterate(std::views::all(array)); + [[maybe_unused]] + auto sequences = iterate(std::views::all(array)); using sequences_t = decltype(sequences); - STATIC_REQUIRE(ex::__ok< - item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - stdexec::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>); - [[maybe_unused]] auto merged = merge_each(sequences); + [[maybe_unused]] + auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok< - item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - stdexec::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>); STATIC_REQUIRE(ex::__callable); int count = 0; - auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ - ++count; - UNSCOPED_INFO("item: " << x - << ", on thread id: " << std::this_thread::get_id()); - }))); + auto v = ex::sync_wait(ignore_all_values( + merged | then_each([&count](int x) { + ++count; + UNSCOPED_INFO("item: " << x << ", on thread id: " << std::this_thread::get_id()); + }))); CHECK(count == 40); CHECK(v.has_value() == true); @@ -264,84 +272,86 @@ namespace { using range_sequence_t = stdexec::__call_result_t; STATIC_REQUIRE(__well_formed_sequence_sender); - STATIC_REQUIRE_FALSE(std::same_as< - item_types_of_t, - item_types<>>); - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_error_t(std::exception_ptr), - ex::set_stopped_t(), - ex::set_value_t()>>); + STATIC_REQUIRE_FALSE(std::same_as, item_types<>>); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_error_t(std::exception_ptr), + ex::set_stopped_t(), + ex::set_value_t() + > + >); using just_range_sender_t = ex::__call_result_t; - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_value_t(range_sequence_t)>>); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures + >); using empty_sequence_t = stdexec::__call_result_t; STATIC_REQUIRE(__well_formed_sequence_sender); - STATIC_REQUIRE(std::same_as< - item_types_of_t, - item_types<>>); - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_value_t()>>); + STATIC_REQUIRE(std::same_as, item_types<>>); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures + >); using just_empty_sender_t = ex::__call_result_t; - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_value_t(empty_sequence_t)>>); - - auto sequences = merge( - ex::just(range(100, 120)), - ex::just(empty_sequence()), - ex::just(range(200, 220))); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures + >); + + auto sequences = + merge(ex::just(range(100, 120)), ex::just(empty_sequence()), ex::just(range(200, 220))); using sequences_t = decltype(sequences); - STATIC_REQUIRE(ex::__ok< - __item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - ex::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok<__item_types_of_t>); + STATIC_REQUIRE(ex::__ok>); - STATIC_REQUIRE(__equivalent< - __item_types_of_t, - item_types>); - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_stopped_t(), - ex::set_value_t()>>); + STATIC_REQUIRE( + __equivalent< + __item_types_of_t, + item_types + >); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures + >); auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok< - __item_types_of_t>); - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_error_t(std::exception_ptr), - ex::set_stopped_t(), - ex::set_value_t()>>); + STATIC_REQUIRE(ex::__ok<__item_types_of_t>); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_error_t(std::exception_ptr), + ex::set_stopped_t(), + ex::set_value_t() + > + >); int count = 0; - auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ - ++count; - UNSCOPED_INFO("item: " << x - << ", on thread id: " << std::this_thread::get_id()); - }))); + auto v = ex::sync_wait(ignore_all_values( + merged | then_each([&count](int x) { + ++count; + UNSCOPED_INFO("item: " << x << ", on thread id: " << std::this_thread::get_id()); + }))); CHECK(count == 40); CHECK(v.has_value() == true); } // TODO - fix problem with stopping -#if 0 +# if 0 TEST_CASE( "merge_each - merge_each sender stops when a nested sequence fails", "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { @@ -365,8 +375,8 @@ namespace { CHECK(count == 20); CHECK(v.has_value() == false); } - #endif // 0 +# endif // 0 #endif // STDEXEC_HAS_STD_RANGES() -} +} // namespace diff --git a/test/exec/sequence/test_merge_each_threaded.cpp b/test/exec/sequence/test_merge_each_threaded.cpp index 7f8266881..e3cb4b80a 100644 --- a/test/exec/sequence/test_merge_each_threaded.cpp +++ b/test/exec/sequence/test_merge_each_threaded.cpp @@ -33,9 +33,9 @@ #include #include -# include -# include -# include +#include +#include +#include namespace { using namespace std::chrono_literals; @@ -44,21 +44,21 @@ namespace { template concept __equivalent = __sequence_sndr::__all_contained_in<_A, _B> - && __sequence_sndr::__all_contained_in<_B, _A> - && ex::__v> - == ex::__v>; + && __sequence_sndr::__all_contained_in<_B, _A> + && ex::__v> + == ex::__v>; struct null_receiver { using __id = null_receiver; using __t = null_receiver; using receiver_concept = ex::receiver_t; - template + template void set_value(_Values&&...) noexcept { } template - void set_error(_Error&& ) noexcept { + void set_error(_Error&&) noexcept { } void set_stopped() noexcept { @@ -70,106 +70,120 @@ namespace { } struct ignore_values_fn_t { - template - void operator()(_Vs&&...) const noexcept {} + template + void operator()(_Vs&&...) const noexcept { + } }; template [[nodiscard]] - auto set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) - -> next_sender auto { - return stdexec::upon_error(stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); + auto + set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) -> next_sender auto { + return stdexec::upon_error( + stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); } }; // a sequence adaptor that applies a function to each item - [[maybe_unused]] static constexpr auto then_each = [](auto f) { + [[maybe_unused]] + static constexpr auto then_each = [](auto f) { return exec::transform_each(ex::then(f)); }; // a sequence adaptor that schedules each item to complete // on the specified scheduler - [[maybe_unused]] static constexpr auto continues_each_on = [](auto sched) { + [[maybe_unused]] + static constexpr auto continues_each_on = [](auto sched) { return exec::transform_each(ex::continues_on(sched)); }; // a sequence adaptor that schedules each item to complete // on the specified scheduler after the specified duration - [[maybe_unused]] static constexpr auto delays_each_on = [](Sched sched, duration_of_t after) noexcept { - auto delay_value = [](Value&& value, Sched sched, duration_of_t after){ + [[maybe_unused]] + static constexpr auto delays_each_on = + [](Sched sched, duration_of_t after) noexcept { + auto delay_value = [](Value&& value, Sched sched, duration_of_t after) { return sequence(schedule_after(sched, after), static_cast(value)); }; - auto delay_adaptor = stdexec::__binder_back>{{sched, after}, {}, {}}; - return exec::transform_each(delay_adaptor); - }; + auto delay_adaptor = + stdexec::__binder_back>{ + {sched, after}, + {}, + {} + }; + return exec::transform_each(delay_adaptor); + }; // a sequence adaptor that applies a function to each item // the function must produce a sequence // all the sequences returned from the function are merged - [[maybe_unused]] static constexpr auto flat_map = [](auto&& f) { + [[maybe_unused]] + static constexpr auto flat_map = [](auto&& f) { auto map_merge = [](auto&& sequence, auto&& f) noexcept { - return merge_each(exec::transform_each( - static_cast(sequence), - ex::then(static_cast(f)))); - }; - return stdexec::__binder_back{{static_cast(f)}, {}, {}}; + return merge_each( + exec::transform_each( + static_cast(sequence), ex::then(static_cast(f)))); + }; + return stdexec::__binder_back{ + {static_cast(f)}, {}, {}}; }; // when_all requires a successful completion // however stop_after_on has no successful completion // this uses variant_sender to add a successful completion // (the successful completion will never occur) - [[maybe_unused]] static constexpr auto with_void = [](auto&& sender) noexcept - -> variant_sender< - stdexec::__call_result_t, - decltype(sender)> { + [[maybe_unused]] + static constexpr auto with_void = [](auto&& sender) noexcept + -> variant_sender, decltype(sender)> { return {static_cast(sender)}; }; // with_stop_token_from adds get_stop_token query, that returns the // token for the provided stop_source, to the receiver env - [[maybe_unused]] static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { + [[maybe_unused]] + static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { return ex::write_env(ex::prop{ex::get_stop_token, stop_source.get_token()}); }; // log_start completes with the provided sequence after printing provided string - [[maybe_unused]] auto log_start = [](auto sequence, auto message) { + [[maybe_unused]] + auto log_start = [](auto sequence, auto message) { return exec::sequence( - ex::read_env(ex::get_stop_token) - | stdexec::then([message](auto&& token) noexcept { - UNSCOPED_INFO(message - << (token.stop_requested() ? ", stop was requested" : ", stop not requested") - << ", on thread id: " << std::this_thread::get_id()); - }), - ex::just(sequence)); + ex::read_env(ex::get_stop_token) | stdexec::then([message](auto&& token) noexcept { + UNSCOPED_INFO( + message << (token.stop_requested() ? ", stop was requested" : ", stop not requested") + << ", on thread id: " << std::this_thread::get_id()); + }), + ex::just(sequence)); }; // log_sequence prints the message when each value in the sequence is emitted - [[maybe_unused]] auto log_sequence = [](auto sequence, auto message) { - return sequence - | then_each([message](auto&& value) mutable noexcept { - UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); - return value; - }); + [[maybe_unused]] + auto log_sequence = [](auto sequence, auto message) { + return sequence | then_each([message](auto&& value) mutable noexcept { + UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); + return value; + }); }; // emits_stopped completes with set_stopped after printing info - [[maybe_unused]] auto emits_stopped = []() { - return ex::just() - | stdexec::let_value([]() noexcept { - UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); - return ex::just_stopped(); - }); + [[maybe_unused]] + auto emits_stopped = []() { + return ex::just() | stdexec::let_value([]() noexcept { + UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); }; // emits_error completes with set_error(error) after printing info - [[maybe_unused]] auto emits_error = [](auto error) { - return ex::just() - | stdexec::let_value([error]() noexcept { - UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); + [[maybe_unused]] + auto emits_error = [](auto error) { + return ex::just() | stdexec::let_value([error]() noexcept { + UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); }; #if STDEXEC_HAS_STD_RANGES() // a sequence of numbers from itoa() - [[maybe_unused]] static constexpr auto range = [](auto from, auto to) { + [[maybe_unused]] + static constexpr auto range = [](auto from, auto to) { return exec::iterate(std::views::iota(from, to)); }; - template + template struct as_sequence_t : Sender { using sender_concept = sequence_sender_t; using item_types = exec::item_types; @@ -198,18 +212,19 @@ namespace { int count = 0; - auto v = ex::sync_wait(ignore_all_values(merged | continues_each_on(sched0) | then_each([&count](int x){ - ++count; - UNSCOPED_INFO("item: " << x - << ", on thread id: " << std::this_thread::get_id()); - }))); + auto v = ex::sync_wait(ignore_all_values( + merged | continues_each_on(sched0) | then_each([&count](int x) { + ++count; + UNSCOPED_INFO("item: " << x << ", on thread id: " << std::this_thread::get_id()); + }))); CHECK(count == 40); CHECK(v.has_value() == true); } TEST_CASE( - "merge_each - merge_each sender stops on failed item while merging all items from multiple threads", + "merge_each - merge_each sender stops on failed item while merging all items from multiple " + "threads", "[sequence_senders][single_thread_context][merge_each][merge][iterate]") { exec::single_thread_context ctx0; @@ -218,63 +233,63 @@ namespace { ex::scheduler auto sched1 = ctx1.get_scheduler(); auto origin = now(sched1); - auto elapsed_ms = [&sched1, origin](){ + auto elapsed_ms = [&sched1, origin]() { using namespace std::chrono; return duration_cast(now(sched1) - origin).count(); }; auto stop_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after) { - return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms]() noexcept { - UNSCOPED_INFO("requesting stop - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_stopped(); - }); - }; + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms]() noexcept { + UNSCOPED_INFO( + "requesting stop - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); + }; - auto error_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { + auto error_after_on = + [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms, error]() noexcept { - UNSCOPED_INFO(error.what() << " - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms, error]() noexcept { + UNSCOPED_INFO( + error.what() << " - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); }; // a sequence whose items are sequences auto sequences = merge( - ex::just(stop_after_on(sched1, 10ms)), // no items - ex::just(range(100, 120)), // int items - ex::just(empty_sequence()), // no items - ex::just(range(200, 220)), // int items - ex::just(error_after_on(sched1, 40ms, - std::runtime_error{"failed sequence "})) // no items + ex::just(stop_after_on(sched1, 10ms)), // no items + ex::just(range(100, 120)), // int items + ex::just(empty_sequence()), // no items + ex::just(range(200, 220)), // int items + ex::just(error_after_on(sched1, 40ms, std::runtime_error{"failed sequence "})) // no items ); // apply delays_each_on to every sequence item and // merge all the new sequences - auto merged = sequences - | flat_map([sched1](auto sequence){ - return sequence | delays_each_on(sched1, 10ms); - }); + auto merged = sequences | flat_map([sched1](auto sequence) { + return sequence | delays_each_on(sched1, 10ms); + }); int count = 0; auto v = ex::sync_wait( ex::when_all( with_void(stop_after_on(sched1, 50ms)), - ignore_all_values(merged - | continues_each_on(sched0) // serializes output on the sched0 strand - | then_each([&count, elapsed_ms](int x){ + ignore_all_values( + merged | continues_each_on(sched0) // serializes output on the sched0 strand + | then_each([&count, elapsed_ms](int x) { ++count; - UNSCOPED_INFO("item: " << x - << ", arrived at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); + UNSCOPED_INFO( + "item: " << x << ", arrived at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); return count; - })) - )); + })))); CHECK(v.has_value() == false); CHECK(count < 40); @@ -291,63 +306,63 @@ namespace { ex::scheduler auto sched1 = ctx1.get_scheduler(); auto origin = now(sched1); - auto elapsed_ms = [&sched1, origin](){ + auto elapsed_ms = [&sched1, origin]() { using namespace std::chrono; return duration_cast(now(sched1) - origin).count(); }; auto stop_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after) { - return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms]() noexcept { - UNSCOPED_INFO("requesting stop - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_stopped(); - }); - }; + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms]() noexcept { + UNSCOPED_INFO( + "requesting stop - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); + }; - auto error_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { + auto error_after_on = + [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms, error]() noexcept { - UNSCOPED_INFO(error.what() << " - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms, error]() noexcept { + UNSCOPED_INFO( + error.what() << " - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); }; // a sequence whose items are sequences auto sequences = merge( - ex::just(stop_after_on(sched1, 10ms)), // no items - ex::just(range(100, 120)), // int items - ex::just(empty_sequence()), // no items - ex::just(range(200, 220)), // int items - ex::just(error_after_on(sched1, 50ms, - std::runtime_error{"failed sequence "})) // no items + ex::just(stop_after_on(sched1, 10ms)), // no items + ex::just(range(100, 120)), // int items + ex::just(empty_sequence()), // no items + ex::just(range(200, 220)), // int items + ex::just(error_after_on(sched1, 50ms, std::runtime_error{"failed sequence "})) // no items ); // apply delays_each_on to every sequence item and // merge all the new sequences - auto merged = sequences - | flat_map([sched1](auto sequence){ - return sequence | delays_each_on(sched1, 10ms); - }); + auto merged = sequences | flat_map([sched1](auto sequence) { + return sequence | delays_each_on(sched1, 10ms); + }); int count = 0; auto v = ex::sync_wait( ex::when_all( with_void(stop_after_on(sched1, 40ms)), - ignore_all_values(merged - | continues_each_on(sched0) // serializes output on the sched0 strand - | then_each([&count, elapsed_ms](int x){ + ignore_all_values( + merged | continues_each_on(sched0) // serializes output on the sched0 strand + | then_each([&count, elapsed_ms](int x) { ++count; - UNSCOPED_INFO("item: " << x - << ", arrived at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); + UNSCOPED_INFO( + "item: " << x << ", arrived at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); return count; - })) - )); + })))); CHECK(v.has_value() == false); CHECK(count < 40); @@ -356,4 +371,4 @@ namespace { #endif // STDEXEC_HAS_STD_RANGES() -} +} // namespace diff --git a/test/exec/test_sequence_senders.cpp b/test/exec/test_sequence_senders.cpp index 009c4fbc8..09a03a692 100644 --- a/test/exec/test_sequence_senders.cpp +++ b/test/exec/test_sequence_senders.cpp @@ -133,7 +133,9 @@ namespace { using item_types = exec::item_types>; template - friend auto tag_invoke(subscribe_t, some_sequence_sender_of , R&& ) -> nop_operation { return {}; } + friend auto tag_invoke(subscribe_t, some_sequence_sender_of, R&&) -> nop_operation { + return {}; + } }; TEST_CASE("sequence_senders - Test for subscribe", "[sequence_senders]") { From 3cbf83b6fa5724e7701b65fe9d522bedc0762815 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Fri, 31 Oct 2025 09:44:23 -0700 Subject: [PATCH 27/39] fix bad copy/paste --- test/exec/sequence/test_merge_each.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/exec/sequence/test_merge_each.cpp b/test/exec/sequence/test_merge_each.cpp index ea1f44d02..f76306a1f 100644 --- a/test/exec/sequence/test_merge_each.cpp +++ b/test/exec/sequence/test_merge_each.cpp @@ -99,7 +99,7 @@ namespace { // on the specified scheduler after the specified duration [[maybe_unused]] static constexpr auto delays_each_on = - [](auto sched, duration_of_t after) noexcept { + [](Sched sched, duration_of_t after) noexcept { auto delay_value = [](Value&& value, Sched sched, duration_of_t after) { return sequence(schedule_after(sched, after), static_cast(value)); }; From ec1479701561050901e4ebf99bd503460c2e8307 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Fri, 31 Oct 2025 09:58:55 -0700 Subject: [PATCH 28/39] clang-format changes --- include/exec/__detail/__basic_sequence.hpp | 54 +- include/exec/sequence/iterate.hpp | 72 +-- include/exec/sequence/marbles.hpp | 588 +++++++++--------- include/exec/sequence/merge.hpp | 83 +-- include/exec/sequence/merge_each.hpp | 503 ++++++++------- include/exec/sequence/notification.hpp | 169 ++--- include/exec/sequence/test_scheduler.hpp | 408 ++++++------ include/exec/sequence_senders.hpp | 259 ++++---- test/exec/sequence/test_marbles.cpp | 75 +-- test/exec/sequence/test_merge_each.cpp | 295 +++++---- .../sequence/test_merge_each_threaded.cpp | 274 ++++---- test/exec/sequence/test_test_scheduler.cpp | 160 +++-- test/test_common/sequences.hpp | 124 ++-- 13 files changed, 1578 insertions(+), 1486 deletions(-) diff --git a/include/exec/__detail/__basic_sequence.hpp b/include/exec/__detail/__basic_sequence.hpp index 102958364..cab9f6ac8 100644 --- a/include/exec/__detail/__basic_sequence.hpp +++ b/include/exec/__detail/__basic_sequence.hpp @@ -80,16 +80,14 @@ namespace exec { } }; template _Self, class... _Env> - requires - (stdexec::__is_debug_env<_Env> || ... || false) - || (!stdexec::__callable) + requires(stdexec::__is_debug_env<_Env> || ... || false) + || (!stdexec::__callable) static auto get_completion_signatures(_Self&& __self, _Env&&... __env) { - return __self.__tag().get_completion_signatures( - static_cast<_Self&&>(__self), - static_cast<_Env&&>(__env)...); + return __self.__tag() + .get_completion_signatures(static_cast<_Self&&>(__self), static_cast<_Env&&>(__env)...); } template _Self, class... _Env> - requires (!stdexec::__is_debug_env<_Env> && ... && true) + requires(!stdexec::__is_debug_env<_Env> && ... && true) static auto get_completion_signatures(_Self&& __self, _Env&&... __env) -> decltype(__self.__tag().get_completion_signatures( static_cast<_Self&&>(__self), @@ -102,28 +100,25 @@ namespace exec { // trailing return-type when it is valid struct get_item_types_sfinae { template _Self, class... _Env> - auto operator()(_Self&& __self, _Env&&... __env) const - -> decltype(__self.__tag().get_item_types( - static_cast<_Self&&>(__self), - static_cast<_Env&&>(__env)...)) { + auto + operator()(_Self&& __self, _Env&&... __env) const -> decltype(__self.__tag().get_item_types( + static_cast<_Self&&>(__self), + static_cast<_Env&&>(__env)...)) { return {}; } }; template _Self, class... _Env> - requires - (stdexec::__is_debug_env<_Env> || ... || false) - || (!stdexec::__callable) + requires(stdexec::__is_debug_env<_Env> || ... || false) + || (!stdexec::__callable) static auto get_item_types(_Self&& __self, _Env&&... __env) { - return __self.__tag().get_item_types( - static_cast<_Self&&>(__self), - static_cast<_Env&&>(__env)...); + return __self.__tag() + .get_item_types(static_cast<_Self&&>(__self), static_cast<_Env&&>(__env)...); } template _Self, class... _Env> - requires (!stdexec::__is_debug_env<_Env> && ... && true) + requires(!stdexec::__is_debug_env<_Env> && ... && true) static auto get_item_types(_Self&& __self, _Env&&... __env) - -> decltype(__self.__tag().get_item_types( - static_cast<_Self&&>(__self), - static_cast<_Env&&>(__env)...)) { + -> decltype(__self.__tag() + .get_item_types(static_cast<_Self&&>(__self), static_cast<_Env&&>(__env)...)) { return {}; } @@ -140,15 +135,14 @@ namespace exec { } }; template _Self, stdexec::receiver _Receiver> - requires - stdexec::__is_debug_env> - || (!stdexec::__callable) + requires stdexec::__is_debug_env> + || (!stdexec::__callable) static auto subscribe(_Self&& __self, _Receiver&& __rcvr) noexcept(noexcept( __self.__tag().subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)))) { return __tag_t::subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)); } template _Self, stdexec::receiver _Receiver> - requires (!stdexec::__is_debug_env>) + requires(!stdexec::__is_debug_env>) static auto subscribe(_Self&& __self, _Receiver&& __rcvr) noexcept(noexcept( __self.__tag().subscribe(static_cast<_Self&&>(__self), static_cast<_Receiver&&>(__rcvr)))) -> decltype(__self.__tag() @@ -157,11 +151,11 @@ namespace exec { } template - static auto - apply(_Sequence&& __sequence, _ApplyFn&& __fun) noexcept(stdexec::__nothrow_callable< - stdexec::__detail::__impl_of<_Sequence>, - stdexec::__copy_cvref_fn<_Sequence>, - _ApplyFn + static auto apply(_Sequence&& __sequence, _ApplyFn&& __fun) + noexcept(stdexec::__nothrow_callable< + stdexec::__detail::__impl_of<_Sequence>, + stdexec::__copy_cvref_fn<_Sequence>, + _ApplyFn >) -> stdexec::__call_result_t< stdexec::__detail::__impl_of<_Sequence>, diff --git a/include/exec/sequence/iterate.hpp b/include/exec/sequence/iterate.hpp index 78c6cd268..5bea69e4d 100644 --- a/include/exec/sequence/iterate.hpp +++ b/include/exec/sequence/iterate.hpp @@ -40,9 +40,11 @@ namespace exec { using namespace stdexec; template - using __range_of_t = stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Data)>; + using __range_of_t = + stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Data)>; template - using __scheduler_of_t = stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Data)>; + using __scheduler_of_t = + stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Data)>; template struct __operation_base { @@ -116,7 +118,9 @@ namespace exec { }; struct trampoline_t { - operator trampoline_scheduler () const noexcept { return {}; } + operator trampoline_scheduler() const noexcept { + return {}; + } }; template @@ -139,7 +143,8 @@ namespace exec { __op_{}; void __start_next() noexcept { - if (stdexec::get_stop_token(this->__rcvr_).stop_requested() + if ( + stdexec::get_stop_token(this->__rcvr_).stop_requested() || this->__iterator_ == this->__sentinel_) { stdexec::set_value(static_cast<_Receiver&&>(__rcvr_)); } else { @@ -171,15 +176,14 @@ namespace exec { template using __scheduler_t = stdexec::__if_c< - stdexec::__decays_to> - , trampoline_scheduler - , __scheduler_of_t<_Data>>; + stdexec::__decays_to>, + trampoline_scheduler, + __scheduler_of_t<_Data> + >; template - using __operation_t = __t<__operation< - __scheduler_t<_Data> - , __decay_t<__range_of_t<_Data>> - , _ReceiverId>>; + using __operation_t = + __t<__operation<__scheduler_t<_Data>, __decay_t<__range_of_t<_Data>>, _ReceiverId>>; template auto operator()(__ignore, _Data&& __data) noexcept(__nothrow_move_constructible<_Receiver>) @@ -196,21 +200,16 @@ namespace exec { struct iterate_t { template requires __decay_copyable<_Range> - auto operator()(_Range&& __range) const - -> __well_formed_sequence_sender auto { + auto operator()(_Range&& __range) const -> __well_formed_sequence_sender auto { return make_sequence_expr( - __decayed_tuple{ - trampoline_t{} - , static_cast<_Range&&>(__range)}); + __decayed_tuple{trampoline_t{}, static_cast<_Range&&>(__range)}); } template requires __decay_copyable<_Range> && __decay_copyable<_Scheduler> auto operator()(_Scheduler&& __scheduler, _Range&& __range) const -> __well_formed_sequence_sender auto { - return make_sequence_expr( - __decayed_tuple<_Scheduler, _Range>{ - static_cast<_Scheduler&&>(__scheduler) - , static_cast<_Range&&>(__range)}); + return make_sequence_expr(__decayed_tuple<_Scheduler, _Range>{ + static_cast<_Scheduler&&>(__scheduler), static_cast<_Range&&>(__range)}); } using __completion_sigs = @@ -218,23 +217,24 @@ namespace exec { template using __scheduler_t = stdexec::__if_c< - stdexec::__decays_to> - , trampoline_scheduler - , __scheduler_of_t<_Data>>; + stdexec::__decays_to>, + trampoline_scheduler, + __scheduler_of_t<_Data> + >; template - using _NextReceiver = stdexec::__t< - __next_receiver< - __scheduler_t<__data_of<_Sequence>> - , __range_of_t<__data_of<_Sequence>> - , __id<_Receiver>>>; + using _NextReceiver = stdexec::__t<__next_receiver< + __scheduler_t<__data_of<_Sequence>>, + __range_of_t<__data_of<_Sequence>>, + __id<_Receiver> + >>; template using __item_sender_t = __result_of< - exec::sequence, - schedule_result_t<__scheduler_t<__data_of<_Sequence>>&>, - __sender_t<__range_of_t<__data_of<_Sequence>>> - >; + exec::sequence, + schedule_result_t<__scheduler_t<__data_of<_Sequence>>&>, + __sender_t<__range_of_t<__data_of<_Sequence>>> + >; template using _NextSender = next_sender_of_t<_Receiver, __item_sender_t<_Sequence>>; @@ -251,14 +251,14 @@ namespace exec { } template _Sequence> - static auto get_completion_signatures(_Sequence&&, __ignore = {}) noexcept - -> __completion_sigs { + static auto + get_completion_signatures(_Sequence&&, __ignore = {}) noexcept -> __completion_sigs { return {}; } template _Sequence> - static auto get_item_types(_Sequence&&, __ignore) noexcept - -> item_types<__item_sender_t<_Sequence>> { + static auto + get_item_types(_Sequence&&, __ignore) noexcept -> item_types<__item_sender_t<_Sequence>> { return {}; } diff --git a/include/exec/sequence/marbles.hpp b/include/exec/sequence/marbles.hpp index 64f284544..d5e437541 100644 --- a/include/exec/sequence/marbles.hpp +++ b/include/exec/sequence/marbles.hpp @@ -96,11 +96,9 @@ namespace exec { return __c_; } - friend auto operator==( - const __value_t& __lhs, - const __value_t& __rhs) noexcept -> bool { - return __lhs.__c_ == __rhs.__c_; - } + friend auto operator==(const __value_t& __lhs, const __value_t& __rhs) noexcept -> bool { + return __lhs.__c_ == __rhs.__c_; + } friend std::string to_string(const __value_t& __self) noexcept { using std::to_string; @@ -121,42 +119,55 @@ namespace exec { }; struct sequence_start_t { - operator marble_selector_t() const noexcept { return marble_selector_t::sequence_start; } + operator marble_selector_t() const noexcept { + return marble_selector_t::sequence_start; + } }; static constexpr inline sequence_start_t sequence_start; struct sequence_connect_t { - operator marble_selector_t() const noexcept { return marble_selector_t::sequence_connect; } + operator marble_selector_t() const noexcept { + return marble_selector_t::sequence_connect; + } }; static constexpr inline sequence_connect_t sequence_connect; struct sequence_end_t { - operator marble_selector_t() const noexcept { return marble_selector_t::sequence_value; } + operator marble_selector_t() const noexcept { + return marble_selector_t::sequence_value; + } }; static constexpr inline sequence_end_t sequence_end; struct sequence_error_t { - operator marble_selector_t() const noexcept { return marble_selector_t::sequence_error; } + operator marble_selector_t() const noexcept { + return marble_selector_t::sequence_error; + } }; static constexpr inline sequence_error_t sequence_error; struct sequence_stopped_t { - operator marble_selector_t() const noexcept { return marble_selector_t::sequence_stopped; } + operator marble_selector_t() const noexcept { + return marble_selector_t::sequence_stopped; + } }; static constexpr inline sequence_stopped_t sequence_stopped; struct request_stop_t { - operator marble_selector_t() const noexcept { return marble_selector_t::request_stop; } + operator marble_selector_t() const noexcept { + return marble_selector_t::request_stop; + } }; static constexpr inline request_stop_t request_stop; using __completion_signatures_t = completion_signatures< - set_value_t(__value_t) - , set_error_t(std::error_code) - , set_error_t(std::exception_ptr) - , set_stopped_t()>; + set_value_t(__value_t), + set_error_t(std::error_code), + set_error_t(std::exception_ptr), + set_stopped_t() + >; - template + template struct marble_t { using __frame_t = typename _Clock::time_point; using __duration_t = typename _Clock::duration; @@ -173,31 +184,31 @@ namespace exec { : __at_{__at} , __selector_{selector::notification} , __notification_{} { - __notification_.emplace(__tag, __error); + __notification_.emplace(__tag, __error); } marble_t(__frame_t __at, set_error_t __tag, std::exception_ptr __ex) noexcept : __at_{__at} , __selector_{selector::notification} , __notification_{} { - __notification_.emplace(__tag, __ex); + __notification_.emplace(__tag, __ex); } marble_t(__frame_t __at, set_value_t __tag, char __c) noexcept : __at_{__at} , __selector_{selector::notification} , __notification_{} { - __notification_.emplace(__tag, __value_t{__c}); + __notification_.emplace(__tag, __value_t{__c}); } marble_t(__frame_t __at, set_value_t __tag, __value_t __v) noexcept : __at_{__at} , __selector_{selector::notification} , __notification_{} { - __notification_.emplace(__tag, __v); + __notification_.emplace(__tag, __v); } marble_t(__frame_t __at, set_stopped_t __tag) noexcept : __at_{__at} , __selector_{selector::notification} , __notification_{} { - __notification_.emplace(__tag); + __notification_.emplace(__tag); } marble_t(__frame_t __at, sequence_start_t __tag) noexcept : __at_{__at} @@ -235,14 +246,14 @@ namespace exec { , __notification_{} { } - template + template void visit(_Fn&& __fn) noexcept { if (__notification_.has_value()) { __notification_->visit(static_cast<_Fn&&>(__fn)); } } - template + template void visit_receiver(_Receiver&& __receiver) noexcept { if (__selector_ == selector::notification) { __notification_->visit_receiver(static_cast<_Receiver&&>(__receiver)); @@ -251,67 +262,76 @@ namespace exec { } } - template + template void visit_sequence_receiver(_Receiver&& __receiver) noexcept { switch (__selector_) { - case selector::sequence_value: { + case selector::sequence_value: { + stdexec::set_value(static_cast<_Receiver&&>(__receiver)); + break; + } + case selector::sequence_error: { + stdexec::set_error(static_cast<_Receiver&&>(__receiver), std::exception_ptr{}); + break; + } + case selector::notification: { + if (value_notification()) { stdexec::set_value(static_cast<_Receiver&&>(__receiver)); break; } - case selector::sequence_error: { - stdexec::set_error(static_cast<_Receiver&&>(__receiver), std::exception_ptr{}); - break; - } - case selector::notification: { - if (value_notification()) { - stdexec::set_value(static_cast<_Receiver&&>(__receiver)); - break; - } - } + } [[fallthrough]]; - case selector::request_stop: + case selector::request_stop: [[fallthrough]]; - case selector::sequence_stopped: + case selector::sequence_stopped: [[fallthrough]]; - case selector::sequence_connect: + case selector::sequence_connect: [[fallthrough]]; - case selector::sequence_start: + case selector::sequence_start: [[fallthrough]]; - case selector::frame_only: + case selector::frame_only: [[fallthrough]]; - default: { - stdexec::set_stopped(static_cast<_Receiver&&>(__receiver)); - break; - } + default: { + stdexec::set_stopped(static_cast<_Receiver&&>(__receiver)); + break; + } }; } - [[nodiscard]] auto visit_sender() noexcept -> __marble_sender_t { + [[nodiscard]] + auto visit_sender() noexcept -> __marble_sender_t { return __notification_->visit_sender(); } - [[nodiscard]] bool sequence_end() const noexcept { + [[nodiscard]] + bool sequence_end() const noexcept { return __selector_ == selector::sequence_value; } - [[nodiscard]] bool sequence_error() const noexcept { + [[nodiscard]] + bool sequence_error() const noexcept { return __selector_ == selector::sequence_error; } - [[nodiscard]] bool sequence_stopped() const noexcept { + [[nodiscard]] + bool sequence_stopped() const noexcept { return __selector_ == selector::sequence_stopped; } - [[nodiscard]] bool request_stop() const noexcept { + [[nodiscard]] + bool request_stop() const noexcept { return __selector_ == selector::request_stop; } - [[nodiscard]] bool value_notification() const noexcept { + [[nodiscard]] + bool value_notification() const noexcept { return __notification_.has_value() && __notification_->value(); } - [[nodiscard]] bool error_notification() const noexcept { + [[nodiscard]] + bool error_notification() const noexcept { return __notification_.has_value() && __notification_->error(); } - [[nodiscard]] bool stopped_notification() const noexcept { + [[nodiscard]] + bool stopped_notification() const noexcept { return __notification_.has_value() && __notification_->stopped(); } - [[nodiscard]] __frame_t frame() const noexcept { + [[nodiscard]] + __frame_t frame() const noexcept { return __at_; } __frame_t shift_frame_by(__duration_t __by) noexcept { @@ -325,69 +345,69 @@ namespace exec { return __old_frame; } - friend auto operator==( - const marble_t& __lhs, - const marble_t& __rhs) noexcept -> bool { - return std::chrono::duration_cast(__lhs.__at_.time_since_epoch()) - == std::chrono::duration_cast(__rhs.__at_.time_since_epoch()) + friend auto operator==(const marble_t& __lhs, const marble_t& __rhs) noexcept -> bool { + return std::chrono::duration_cast(__lhs.__at_.time_since_epoch()) + == std::chrono::duration_cast(__rhs.__at_ + .time_since_epoch()) && __lhs.__selector_ == __rhs.__selector_ && __lhs.__notification_.has_value() == __rhs.__notification_.has_value() && (__lhs.__notification_.has_value() && __rhs.__notification_.has_value() - ? __lhs.__notification_.value() == __rhs.__notification_.value() - : true); - } + ? __lhs.__notification_.value() == __rhs.__notification_.value() + : true); + } friend std::string to_string(const marble_t& __self) noexcept { using std::to_string; std::string __result; switch (__self.__selector_) { - case selector::frame_only: { - __result = "frame"; - break; - } - case selector::notification: { - __result = to_string(__self.__notification_.value()); - break; - } - case selector::request_stop: { - __result = to_string(__marbles::request_stop) + "()"; - break; - } - case selector::sequence_start: { - __result = to_string(__marbles::sequence_start) + "()"; - break; - } - case selector::sequence_connect: { - __result = to_string(__marbles::sequence_connect) + "()"; - break; - } - case selector::sequence_value: { - __result = to_string(__marbles::sequence_end) + "()"; - break; - } - case selector::sequence_error: { - __result = to_string(__marbles::sequence_error) + "()"; - break; - } - case selector::sequence_stopped: { - __result = to_string(__marbles::sequence_stopped) + "()"; - break; - } - default: { - return {"uninitialized-marble"}; - } + case selector::frame_only: { + __result = "frame"; + break; + } + case selector::notification: { + __result = to_string(__self.__notification_.value()); + break; + } + case selector::request_stop: { + __result = to_string(__marbles::request_stop) + "()"; + break; + } + case selector::sequence_start: { + __result = to_string(__marbles::sequence_start) + "()"; + break; + } + case selector::sequence_connect: { + __result = to_string(__marbles::sequence_connect) + "()"; + break; + } + case selector::sequence_value: { + __result = to_string(__marbles::sequence_end) + "()"; + break; + } + case selector::sequence_error: { + __result = to_string(__marbles::sequence_error) + "()"; + break; + } + case selector::sequence_stopped: { + __result = to_string(__marbles::sequence_stopped) + "()"; + break; + } + default: { + return {"uninitialized-marble"}; + } }; - return - __result - + "@" - + to_string(std::chrono::duration_cast(__self.__at_.time_since_epoch()).count()) - + "ms"; + return __result + "@" + + to_string( + std::chrono::duration_cast(__self.__at_ + .time_since_epoch()) + .count()) + + "ms"; } }; struct get_marbles_from_t { - template + template constexpr auto operator()(_Clock __clock, __mstring<_Len> __diagram) const noexcept -> std::vector> { using __frame_t = typename _Clock::time_point; @@ -406,127 +426,128 @@ namespace exec { __remaining = __remaining.subspan(__skip); }; auto __push = [&](auto __tag, auto... __args) noexcept { - __marbles.emplace_back( - __group_start_frame == __frame_t{-1ms} ? __frame : __group_start_frame - , __tag, __args...); + __marbles.emplace_back( + __group_start_frame == __frame_t{-1ms} ? __frame : __group_start_frame, + __tag, + __args...); }; - while(!__remaining.empty()) { + while (!__remaining.empty()) { __frame_t __next_frame{__frame}; - auto __advance_frame_by = [&__next_frame, &__group_start_frame](__duration_t __by) noexcept { - __next_frame += __group_start_frame == __frame_t{-1ms} - ? __by - : 0ms; + auto __advance_frame_by = [&__next_frame, + &__group_start_frame](__duration_t __by) noexcept { + __next_frame += __group_start_frame == __frame_t{-1ms} ? __by : 0ms; }; switch (__remaining.front()) { - case '-': { - __advance_frame_by(1ms); - __consume_first(1); - break; - } - case '(': { - __group_start_frame = __frame; - __consume_first(1); - break; - } - case ')': { - __group_start_frame = __frame_t{-1ms}; - __advance_frame_by(1ms); - __consume_first(1); - break; - } - case '|': { - __push(sequence_end); - __consume_first(1); - break; - } - case '=': { - __push(sequence_connect); - __consume_first(1); - break; - } - case '^': { - __push(sequence_start); - __consume_first(1); - break; - } - case '$': { - __push(sequence_stopped); - __consume_first(1); - break; - } - case '?': { - __push(request_stop); - __consume_first(1); - break; - } - case '#': { - __push(set_error, std::make_error_code(std::errc::interrupted)); - __consume_first(1); - break; - } - case '.': { - __push(set_stopped); - __consume_first(1); - break; - } - default: { - long __consumed_in_default = 0; - if (__whole.begin() == __remaining.begin() || !!std::isspace(__remaining.front())) { - if (!!std::isspace(__remaining.front())) { - __consume_first(1); - ++__consumed_in_default; - } - // try to consume a duration at first char or after ' ' char. - if (!!std::isdigit(__remaining.front())) { - auto __valid_duration_suffix = [](auto c) noexcept { - return c == 'm' || c == 's'; - }; - auto __suffix_begin = std::ranges::find_if(__remaining, __valid_duration_suffix); - bool __all_digits = std::all_of( - __remaining.begin() - , __suffix_begin - , [](auto c){ return std::isdigit(c); }); - if ( - __suffix_begin != __remaining.end() - && __suffix_begin - __remaining.begin() > 0 - && __all_digits) { - long __to_consume = __suffix_begin - __remaining.begin(); - long __duration = std::atol(__remaining.data()); - if (std::ranges::equal(__remaining.subspan(__to_consume, 3), __make_span("ms "_mstr))) { - __to_consume += 2; - } else if (std::ranges::equal(__remaining.subspan(__to_consume, 2), __make_span("s "_mstr))) { - __duration *= 1000; - __to_consume += 1; - } else if (std::ranges::equal(__remaining.subspan(__to_consume, 2), __make_span("m "_mstr))) { - __duration = __duration * 1000 * 60; - __to_consume += 1; - } else { - __duration = -1; - __to_consume = 0; - //fallthrough - } - if (__duration >= 0 && __to_consume > 0) { - __advance_frame_by(std::chrono::milliseconds(__duration)); - __consume_first(__to_consume); - __consumed_in_default += __to_consume; - break; - } - } - } - } - if (!!std::isalnum(__remaining.front())) { - __advance_frame_by(1ms); - __push(set_value, __remaining.front()); + case '-': { + __advance_frame_by(1ms); + __consume_first(1); + break; + } + case '(': { + __group_start_frame = __frame; + __consume_first(1); + break; + } + case ')': { + __group_start_frame = __frame_t{-1ms}; + __advance_frame_by(1ms); + __consume_first(1); + break; + } + case '|': { + __push(sequence_end); + __consume_first(1); + break; + } + case '=': { + __push(sequence_connect); + __consume_first(1); + break; + } + case '^': { + __push(sequence_start); + __consume_first(1); + break; + } + case '$': { + __push(sequence_stopped); + __consume_first(1); + break; + } + case '?': { + __push(request_stop); + __consume_first(1); + break; + } + case '#': { + __push(set_error, std::make_error_code(std::errc::interrupted)); + __consume_first(1); + break; + } + case '.': { + __push(set_stopped); + __consume_first(1); + break; + } + default: { + long __consumed_in_default = 0; + if (__whole.begin() == __remaining.begin() || !!std::isspace(__remaining.front())) { + if (!!std::isspace(__remaining.front())) { __consume_first(1); ++__consumed_in_default; - break; } - if (__consumed_in_default == 0) { - // parsing error - return __marbles; + // try to consume a duration at first char or after ' ' char. + if (!!std::isdigit(__remaining.front())) { + auto __valid_duration_suffix = [](auto c) noexcept { + return c == 'm' || c == 's'; + }; + auto __suffix_begin = std::ranges::find_if(__remaining, __valid_duration_suffix); + bool __all_digits = std::all_of(__remaining.begin(), __suffix_begin, [](auto c) { + return std::isdigit(c); + }); + if ( + __suffix_begin != __remaining.end() && __suffix_begin - __remaining.begin() > 0 + && __all_digits) { + long __to_consume = __suffix_begin - __remaining.begin(); + long __duration = std::atol(__remaining.data()); + if (std::ranges::equal( + __remaining.subspan(__to_consume, 3), __make_span("ms "_mstr))) { + __to_consume += 2; + } else if (std::ranges::equal( + __remaining.subspan(__to_consume, 2), __make_span("s "_mstr))) { + __duration *= 1000; + __to_consume += 1; + } else if (std::ranges::equal( + __remaining.subspan(__to_consume, 2), __make_span("m "_mstr))) { + __duration = __duration * 1000 * 60; + __to_consume += 1; + } else { + __duration = -1; + __to_consume = 0; + //fallthrough + } + if (__duration >= 0 && __to_consume > 0) { + __advance_frame_by(std::chrono::milliseconds(__duration)); + __consume_first(__to_consume); + __consumed_in_default += __to_consume; + break; + } + } } + } + if (!!std::isalnum(__remaining.front())) { + __advance_frame_by(1ms); + __push(set_value, __remaining.front()); + __consume_first(1); + ++__consumed_in_default; break; } + if (__consumed_in_default == 0) { + // parsing error + return __marbles; + } + break; + } }; __frame = __next_frame; } @@ -544,15 +565,17 @@ namespace exec { std::vector>* __recording_; _Receiver* __receiver_; - template + template void set_value(_Args&&... __args) noexcept { - __recording_->emplace_back(__clock_.now(), stdexec::set_value, static_cast<_Args&&>(__args)...); + __recording_ + ->emplace_back(__clock_.now(), stdexec::set_value, static_cast<_Args&&>(__args)...); stdexec::set_value(static_cast<_Receiver>(*__receiver_)); } - template + template void set_error(_Error&& __error) noexcept { - __recording_->emplace_back(__clock_.now(), stdexec::set_error, static_cast<_Error&&>(__error)); + __recording_ + ->emplace_back(__clock_.now(), stdexec::set_error, static_cast<_Error&&>(__error)); stdexec::set_stopped(static_cast<_Receiver>(*__receiver_)); } @@ -580,12 +603,18 @@ namespace exec { std::vector>* __recording_; stdexec::connect_result_t<_Value, __receiver_t> __op_; - __t(_Value&& __value, _Receiver&& __receiver, _Clock __clock, std::vector>* __recording) noexcept + __t( + _Value&& __value, + _Receiver&& __receiver, + _Clock __clock, + std::vector>* __recording) noexcept : __receiver_{static_cast<_Receiver&&>(__receiver)} , __clock_{__clock} , __recording_(__recording) - , __op_{stdexec::connect(static_cast<_Value&&>(__value), __receiver_t{__clock_, __recording_, &__receiver_})} - {} + , __op_{stdexec::connect( + static_cast<_Value&&>(__value), + __receiver_t{__clock_, __recording_, &__receiver_})} { + } void start() & noexcept { stdexec::start(__op_); @@ -593,14 +622,15 @@ namespace exec { }; }; - template + template struct __value_sender { struct __t { using __id = __value_sender; using sender_concept = stdexec::sender_t; template - using __value_operation_t = stdexec::__t<__value_operation<_Value, stdexec::__id<_Receiver>, _Clock>>; + using __value_operation_t = + stdexec::__t<__value_operation<_Value, stdexec::__id<_Receiver>, _Clock>>; _Clock __clock_; std::vector>* __recording_; @@ -615,10 +645,11 @@ namespace exec { template _Self, receiver _Receiver> static auto connect(_Self&& __self, _Receiver&& __rcvr) noexcept(__nothrow_move_constructible<_Receiver>) { - return __value_operation_t<_Receiver>{static_cast<_Value&&>(__self.__value_), - static_cast<_Receiver&&>(__rcvr), - __self.__clock_, - __self.__recording_}; + return __value_operation_t<_Receiver>{ + static_cast<_Value&&>(__self.__value_), + static_cast<_Receiver&&>(__rcvr), + __self.__clock_, + __self.__recording_}; } }; }; @@ -639,14 +670,8 @@ namespace exec { using __receiver_t = __value_receiver<_Receiver, _Clock>; template - auto set_next(_Value&& __value) - noexcept - -> next_sender auto { - return __value_sender_t<_Value>{ - __clock_, - __recording_, - static_cast<_Value&&>(__value) - }; + auto set_next(_Value&& __value) noexcept -> next_sender auto { + return __value_sender_t<_Value>{__clock_, __recording_, static_cast<_Value&&>(__value)}; } void set_value() noexcept { @@ -654,8 +679,8 @@ namespace exec { stdexec::set_value(static_cast<_Receiver&&>(*__receiver_)); } - template - void set_error(_Error&& ) noexcept { + template + void set_error(_Error&&) noexcept { __recording_->emplace_back(__clock_.now(), sequence_error); stdexec::set_value(static_cast<_Receiver&&>(*__receiver_)); } @@ -684,14 +709,18 @@ namespace exec { std::vector>* __recording_; exec::subscribe_result_t<_Sequence, __receiver_t> __op_; - __t(_Sequence&& __sequence, _Receiver&& __receiver, _Clock __clock, std::vector>* __recording) noexcept + __t( + _Sequence&& __sequence, + _Receiver&& __receiver, + _Clock __clock, + std::vector>* __recording) noexcept : __receiver_{static_cast<_Receiver&&>(__receiver)} , __clock_{__clock} , __recording_(__recording) , __op_{exec::subscribe( - static_cast<_Sequence&&>(__sequence) - , __receiver_t{__clock_, __recording_, &__receiver_})} - {} + static_cast<_Sequence&&>(__sequence), + __receiver_t{__clock_, __recording_, &__receiver_})} { + } void start() & noexcept { __recording_->emplace_back(__clock_.now(), sequence_start); @@ -706,39 +735,41 @@ namespace exec { using __receiver_id_t = __id<_Receiver>; - template + template using __operation_t = __t<__operation<_Child, __receiver_id_t, _Clock>>; template auto operator()(__ignore, _Data&& __data, _Child&& __child) noexcept(__nothrow_constructible_from< - __operation_t<_Child> - , _Child - , _Receiver - , _Clock - , std::vector>*>) - -> __operation_t<_Child> { + __operation_t<_Child>, + _Child, + _Receiver, + _Clock, + std::vector>* + >) -> __operation_t<_Child> { auto [__recording, __clock] = static_cast<_Data&&>(__data); __recording->emplace_back(__clock.now(), sequence_connect); - return {static_cast<_Child&&>(__child), - static_cast<_Receiver&&>(__rcvr_), - __clock, - __recording}; + return { + static_cast<_Child&&>(__child), static_cast<_Receiver&&>(__rcvr_), __clock, __recording}; } }; struct record_marbles_t { - template - auto operator()(std::vector>* __recording, _Clock __clock, _Sequence&& __sequence) const - {//-> __well_formed_sender auto { + template + auto operator()( + std::vector>* __recording, + _Clock __clock, + _Sequence&& __sequence) const { //-> __well_formed_sender auto { auto __domain = __get_early_domain(static_cast<_Sequence&&>(__sequence)); return transform_sender( - __domain, __make_sexpr( - __decayed_tuple>*, _Clock>{__recording, __clock} - , static_cast<_Sequence&&>(__sequence))); - } - template - std::vector> operator()(_Clock __clock, _Sequence&& __sequence) const noexcept { + __domain, + __make_sexpr( + __decayed_tuple>*, _Clock>{__recording, __clock}, + static_cast<_Sequence&&>(__sequence))); + } + template + std::vector> + operator()(_Clock __clock, _Sequence&& __sequence) const noexcept { std::vector> __recording; auto __recorder = (*this)(&__recording, __clock, static_cast<_Sequence&&>(__sequence)); stdexec::sync_wait(__recorder); @@ -748,28 +779,26 @@ namespace exec { struct __record_marbles_impl : __sexpr_defaults { - template + template using __clock_of_t = __mapply<__q<__mback>, __data_of>; - static constexpr auto get_completion_signatures = [] - _Sender, class... _Env> - (_Sender&&, _Env&&...) noexcept - -> completion_signatures { - return {}; - }; + static constexpr auto get_completion_signatures = + [] _Sender, class... _Env>(_Sender&&, _Env&&...) noexcept + -> completion_signatures { + return {}; + }; - static constexpr auto connect = [] - _Self, receiver _Receiver> - (_Self&& __self, _Receiver&& __rcvr) - noexcept(__nothrow_callable<__sexpr_apply_t, _Self, __connect_fn<__clock_of_t<_Self>, _Receiver>>) - -> __call_result_t<__sexpr_apply_t, _Self, __connect_fn<__clock_of_t<_Self>, _Receiver>> { - return __sexpr_apply( - static_cast<_Self&&>(__self) - , __connect_fn<__clock_of_t<_Self>, _Receiver>{static_cast<_Receiver&&>(__rcvr)}); - }; + static constexpr auto connect = + [] _Self, receiver _Receiver>(_Self&& __self, _Receiver&& __rcvr) noexcept( + __nothrow_callable<__sexpr_apply_t, _Self, __connect_fn<__clock_of_t<_Self>, _Receiver>>) + -> __call_result_t<__sexpr_apply_t, _Self, __connect_fn<__clock_of_t<_Self>, _Receiver>> { + return __sexpr_apply( + static_cast<_Self&&>(__self), + __connect_fn<__clock_of_t<_Self>, _Receiver>{static_cast<_Receiver&&>(__rcvr)}); + }; }; - } // __marbles + } // namespace __marbles using sequence_start_t = __marbles::sequence_start_t; static constexpr inline auto sequence_start = sequence_start_t{}; @@ -789,7 +818,7 @@ namespace exec { using request_stop_t = __marbles::request_stop_t; static constexpr inline auto request_stop = request_stop_t{}; - template + template using marble_t = __marbles::marble_t<_Clock>; using get_marbles_from_t = __marbles::get_marbles_from_t; @@ -807,6 +836,5 @@ namespace exec { namespace stdexec { template <> - struct __sexpr_impl - : exec::__marbles::__record_marbles_impl { }; + struct __sexpr_impl : exec::__marbles::__record_marbles_impl { }; } // namespace stdexec diff --git a/include/exec/sequence/merge.hpp b/include/exec/sequence/merge.hpp index 7c0d6bb0b..8cc1788d9 100644 --- a/include/exec/sequence/merge.hpp +++ b/include/exec/sequence/merge.hpp @@ -57,8 +57,7 @@ namespace exec { static_cast<_Receiver&&>(__op_->__receiver_), static_cast<_Error&&>(__error)); } - void set_stopped() noexcept - { + void set_stopped() noexcept { stdexec::set_stopped(static_cast<_Receiver&&>(__op_->__receiver_)); } @@ -73,26 +72,28 @@ namespace exec { using _Receiver = stdexec::__t<_ReceiverId>; template - auto operator()(_Item&& __item, __operation_base<_Receiver>* __op) const noexcept( - __nothrow_callable) - -> next_sender_of_t<_Receiver, _Item> { - return exec::set_next( - __op->__receiver_, static_cast<_Item&&>(__item)); + auto operator()(_Item&& __item, __operation_base<_Receiver>* __op) const + noexcept(__nothrow_callable) + -> next_sender_of_t<_Receiver, _Item> { + return exec::set_next(__op->__receiver_, static_cast<_Item&&>(__item)); } }; struct __combine { - template - using merge_each_fn_t = __binder_back<__merge_each_fn<_ReceiverId>, __operation_base<__t<_ReceiverId>>*>; - - template - using transform_sender_t = __call_result_t>; - template - using ignored_sender_t = __call_result_t>; - - template - using result_sender_t = __call_result_t...>; + template + using merge_each_fn_t = + __binder_back<__merge_each_fn<_ReceiverId>, __operation_base<__t<_ReceiverId>>*>; + + template + using transform_sender_t = + __call_result_t>; + template + using ignored_sender_t = + __call_result_t>; + + template + using result_sender_t = + __call_result_t...>; }; template @@ -101,23 +102,25 @@ namespace exec { using merge_each_fn_t = typename __combine::merge_each_fn_t<_ReceiverId>; - template - using result_sender_t = typename __combine::result_sender_t<_ReceiverIdDependent, _Sequences...>; + template + using result_sender_t = + typename __combine::result_sender_t<_ReceiverIdDependent, _Sequences...>; struct __t : __operation_base<_Receiver> { using __id = __operation; - connect_result_t, stdexec::__t<__result_receiver<_ReceiverId>>> __op_result_; + connect_result_t, stdexec::__t<__result_receiver<_ReceiverId>>> + __op_result_; __t(_Receiver __rcvr, _Sequences... __sequences) - : __operation_base< - _Receiver - >{static_cast<_Receiver&&>(__rcvr)} + : __operation_base<_Receiver>{static_cast<_Receiver&&>(__rcvr)} , __op_result_{stdexec::connect( - stdexec::when_all( - exec::ignore_all_values( - exec::transform_each(static_cast<_Sequences&&>(__sequences), merge_each_fn_t{{this}, {}, {}}))...), - stdexec::__t<__result_receiver<_ReceiverId>>{this})} { + stdexec::when_all( + exec::ignore_all_values( + exec::transform_each( + static_cast<_Sequences&&>(__sequences), + merge_each_fn_t{{this}, {}, {}}))...), + stdexec::__t<__result_receiver<_ReceiverId>>{this})} { } void start() & noexcept { @@ -132,24 +135,20 @@ namespace exec { template auto operator()(__ignore, __ignore, _Sequences... __sequences) noexcept( - (__nothrow_decay_copyable<_Sequences> && ...) - && __nothrow_move_constructible<_Receiver>) + (__nothrow_decay_copyable<_Sequences> && ...) && __nothrow_move_constructible<_Receiver>) -> __t<__operation<__id<_Receiver>, _Sequences...>> { - return { - static_cast<_Receiver&&>(__rcvr_), - static_cast<_Sequences&&>(__sequences)...}; + return {static_cast<_Receiver&&>(__rcvr_), static_cast<_Sequences&&>(__sequences)...}; } }; struct merge_t { template auto operator()(_Sequences&&... __sequences) const - noexcept((__nothrow_decay_copyable<_Sequences> && ...)) - -> __well_formed_sequence_sender auto { + noexcept((__nothrow_decay_copyable<_Sequences> && ...)) -> __well_formed_sequence_sender + auto { auto __domain = __common_domain_t<_Sequences...>(); return transform_sender( - __domain, make_sequence_expr(__(), - static_cast<_Sequences&&>(__sequences)...)); + __domain, make_sequence_expr(__(), static_cast<_Sequences&&>(__sequences)...)); } template @@ -180,7 +179,7 @@ namespace exec { template _Self, class... _Env> static auto get_completion_signatures(_Self&&, _Env&&...) noexcept { - return __minvoke<__mtry_catch<__q<__completions_t>, __q<__error_t>>, _Self, _Env...>(); + return __minvoke<__mtry_catch<__q<__completions_t>, __q<__error_t>>, _Self, _Env...>(); } template @@ -188,10 +187,12 @@ namespace exec { template using __f = stdexec::__mapply< - stdexec::__munique>, + stdexec::__munique>, stdexec::__minvoke< stdexec::__mconcat>, - __item_types_of_t<_Sequences, _Env...>...>>; + __item_types_of_t<_Sequences, _Env...>... + > + >; }; template @@ -199,7 +200,7 @@ namespace exec { template _Self, class... _Env> static auto get_item_types(_Self&&, _Env&&...) noexcept { - return __minvoke<__mtry_catch<__q<__items_t>, __q<__error_t>>, _Self, _Env...>(); + return __minvoke<__mtry_catch<__q<__items_t>, __q<__error_t>>, _Self, _Env...>(); } template _Self, receiver _Receiver> diff --git a/include/exec/sequence/merge_each.hpp b/include/exec/sequence/merge_each.hpp index 1fb94fa7a..7f9016ff0 100644 --- a/include/exec/sequence/merge_each.hpp +++ b/include/exec/sequence/merge_each.hpp @@ -45,26 +45,24 @@ namespace exec { auto operator()(inplace_stop_token& __stop_token) const noexcept { return stdexec::prop{stdexec::get_stop_token, __stop_token}; } - template + template auto operator()(stdexec::inplace_stop_token& __stop_token, _Env&& __env) const noexcept { return __env::__join((*this)(__stop_token), static_cast<_Env&&>(__env)); } auto operator()(inplace_stop_source& __stop_source) const noexcept { return stdexec::prop{stdexec::get_stop_token, __stop_source.get_token()}; } - template + template auto operator()(stdexec::inplace_stop_source& __stop_source, _Env&& __env) const noexcept { return __env::__join((*this)(__stop_source), static_cast<_Env&&>(__env)); } }; static constexpr inline __env_with_inplace_stop_token_t __env_with_inplace_stop_token; - template - using __env_with_inplace_stop_token_result_t = decltype( - __env_with_inplace_stop_token( - stdexec::__declval(), - stdexec::__declval<_Env>()...) - ); + template + using __env_with_inplace_stop_token_result_t = decltype(__env_with_inplace_stop_token( + stdexec::__declval(), + stdexec::__declval<_Env>()...)); template struct __nested_stop { @@ -80,8 +78,9 @@ namespace exec { using __env_result_t = __env_with_inplace_stop_token_result_t<__env_t>; using __stop_token_t = stop_token_of_t<__env_t>; using __stop_callback_t = stop_callback_for_t<__stop_token_t, __on_stop_request>; - struct no_callback {}; - using __callback_t = __if_c, no_callback, __optional<__stop_callback_t>>; + struct no_callback { }; + using __callback_t = + __if_c, no_callback, __optional<__stop_callback_t>>; inplace_stop_source __stop_source_{}; __callback_t __on_stop_{}; @@ -114,7 +113,7 @@ namespace exec { return false; } - inplace_stop_token get_token() const& noexcept { + inplace_stop_token get_token() const & noexcept { return __stop_source_.get_token(); } @@ -128,8 +127,7 @@ namespace exec { return env_from(get_env(__receiver)); } - using env_t = - decltype(__env_from(std::declval<__nested_stop*>(), __declval<__env_t>())); + using env_t = decltype(__env_from(std::declval<__nested_stop*>(), __declval<__env_t>())); }; template @@ -149,9 +147,10 @@ namespace exec { // The first error to arrive will request_stop on the nested inplace_stop_source // - template + template struct __operation_base_interface { - ~__operation_base_interface(){} + ~__operation_base_interface() { + } virtual void nested_value_started() noexcept = 0; virtual void nested_value_complete() noexcept = 0; virtual bool nested_sequence_fail() noexcept = 0; @@ -163,22 +162,27 @@ namespace exec { __operation_base_interface(_ErrorStorage* __error_storage) noexcept : __error_storage_{__error_storage} - , __token_{} {} + , __token_{} { + } - template + template auto env_from(_Env&& __env) noexcept -> __env_with_inplace_stop_token_result_t<_Env> { return __env_with_inplace_stop_token(__token_, static_cast<_Env&&>(__env)); } - template - requires (!same_as<_Error, _ErrorStorage>) - void store_error(_Error&& __error) - noexcept( - __nothrow_callable), _ErrorStorage&, _Error>) { + template + requires(!same_as<_Error, _ErrorStorage>) + void + store_error(_Error&& __error) noexcept(__nothrow_callable< + decltype(&_ErrorStorage::template emplace<_Error>), + _ErrorStorage&, + _Error + >) { if (this->nested_sequence_fail()) { // We are the first child to complete with an error, so we must save the error. (Any // subsequent errors are ignored.) - if constexpr (noexcept(__error_storage_->template emplace<_Error>(static_cast<_Error&&>(__error)))) { + if constexpr (noexcept(__error_storage_ + ->template emplace<_Error>(static_cast<_Error&&>(__error)))) { __error_storage_->template emplace<_Error>(static_cast<_Error&&>(__error)); } else { STDEXEC_TRY { @@ -215,43 +219,43 @@ namespace exec { stdexec::set_error( static_cast<_ErrorReceiver&&>(__receiver_), static_cast(__error)); - } - , static_cast<_ErrorStorage&&>(*__op_->__error_storage_)); + }, + static_cast<_ErrorStorage&&>(*__op_->__error_storage_)); } }; }; - template + template struct __error_sender { using __operation_base_interface_t = __operation_base_interface<_ErrorStorage>; struct __t { using __id = __error_sender; using sender_concept = stdexec::sender_t; - template + template using __error_op_t = stdexec::__t<__error_op<_ErrorReceiverId, _ErrorStorage>>; - template + template using __error_signature_t = stdexec::set_error_t(_Error); __operation_base_interface_t* __op_; template _Self, class... _Env> - static auto get_completion_signatures(_Self&&, _Env&&...) noexcept - -> stdexec::__mapply< - stdexec::__mtransform< - stdexec::__q<__error_signature_t>, - stdexec::__qq>, - _ErrorStorage> { + static auto get_completion_signatures(_Self&&, _Env&&...) noexcept -> stdexec::__mapply< + stdexec::__mtransform< + stdexec::__q<__error_signature_t>, + stdexec::__qq + >, + _ErrorStorage + > { return {}; } template _Self, receiver _ErrorReceiver> static auto connect(_Self&& __self, _ErrorReceiver&& __rcvr) noexcept(__nothrow_move_constructible<_ErrorReceiver>) - -> __error_op_t> { - return {static_cast<_ErrorReceiver&&>(__rcvr), - __self.__op_}; + -> __error_op_t> { + return {static_cast<_ErrorReceiver&&>(__rcvr), __self.__op_}; } }; }; @@ -288,8 +292,7 @@ namespace exec { _Receiver* __receiver_; __nested_stop_t* __source_; - auto operator()() const noexcept - -> __nested_stop_env_t { + auto operator()() const noexcept -> __nested_stop_env_t { return __source_->env_from(*__receiver_); } }; @@ -306,7 +309,8 @@ namespace exec { using __error_next_sender_t = next_sender_of_t<_Receiver&, __error_sender_t>; using __env_fn_t = __env_fn<_Receiver>; using __error_next_receiver_t = __error_next_receiver<_ErrorStorage, __env_fn_t>; - using __error_op_t = stdexec::connect_result_t<__error_next_sender_t, __error_next_receiver_t>; + using __error_op_t = + stdexec::connect_result_t<__error_next_sender_t, __error_next_receiver_t>; _Receiver __receiver_; _ErrorStorage __error_storage_{}; @@ -316,14 +320,13 @@ namespace exec { __nested_stop_t __nested_stop_{}; stdexec::__optional<__error_op_t> __error_op_{}; - __operation_base(_Receiver __receiver) - noexcept(__nothrow_move_constructible<_Receiver>) + __operation_base(_Receiver __receiver) noexcept(__nothrow_move_constructible<_Receiver>) : __interface_t{&__error_storage_} , __receiver_{static_cast<_Receiver&&>(__receiver)} { - __interface_t::__token_ = __nested_stop_.get_token(); - } + __interface_t::__token_ = __nested_stop_.get_token(); + } - template + template void set_exception(_Error&& __error) noexcept { switch (__completion_.exchange(__completion_t::__error)) { case __completion_t::__started: @@ -355,7 +358,8 @@ namespace exec { // already been requested. __nested_stop_.request_stop(); break; - case __completion_t::__stopped: [[fallthrough]]; // We're already in the "stopped" state. Ignore the break. + case __completion_t::__stopped: + [[fallthrough]]; // We're already in the "stopped" state. Ignore the break. case __completion_t::__error:; // We're already in the "error" state. Ignore the break. } } @@ -421,33 +425,39 @@ namespace exec { case __completion_t::__error: if (__ex_ != nullptr) { // forward error from the subscribed sequence of sequences - stdexec::set_error(static_cast<_Receiver&&>(__receiver_), static_cast(__ex_)); + stdexec::set_error( + static_cast<_Receiver&&>(__receiver_), static_cast(__ex_)); } else { // forward error from the nested sequences as the last item if constexpr ( __nothrow_callable && __nothrow_connectable<__error_next_sender_t, __error_next_receiver_t>) { auto __next_sender = exec::set_next(__receiver_, __error_sender_t{this}); - auto __next_receiver = __error_next_receiver_t{this, __env_fn{&__receiver_, &__nested_stop_}}; + auto __next_receiver = __error_next_receiver_t{ + this, __env_fn{&__receiver_, &__nested_stop_} + }; __error_op_.__emplace_from([&]() { - return stdexec::connect( - static_cast<__error_next_sender_t&&>(__next_sender), - static_cast<__error_next_receiver_t&&>(__next_receiver)); - }); + return stdexec::connect( + static_cast<__error_next_sender_t&&>(__next_sender), + static_cast<__error_next_receiver_t&&>(__next_receiver)); + }); stdexec::start(__error_op_.value()); } else { STDEXEC_TRY { auto __next_sender = exec::set_next(__receiver_, __error_sender_t{this}); - auto __next_receiver = __error_next_receiver_t{this, __env_fn{&__receiver_, &__nested_stop_}}; + auto __next_receiver = __error_next_receiver_t{ + this, __env_fn{&__receiver_, &__nested_stop_} + }; __error_op_.__emplace_from([&]() { - return stdexec::connect( - static_cast<__error_next_sender_t&&>(__next_sender), - static_cast<__error_next_receiver_t&&>(__next_receiver)); - }); + return stdexec::connect( + static_cast<__error_next_sender_t&&>(__next_sender), + static_cast<__error_next_receiver_t&&>(__next_receiver)); + }); stdexec::start(__error_op_.value()); } STDEXEC_CATCH_ALL { - stdexec::set_error(static_cast<_Receiver&&>(__receiver_), std::current_exception()); + stdexec::set_error( + static_cast<_Receiver&&>(__receiver_), std::current_exception()); } } } @@ -487,12 +497,12 @@ namespace exec { __nested_value_operation_base<_NestedValueReceiver>* __nested_value_op_; __operation_base_interface_t* __op_; - template + template void set_value(_Results&&... __results) noexcept { auto __op = __op_; stdexec::set_value( - static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_) - , static_cast<_Results&&>(__results)...); + static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_), + static_cast<_Results&&>(__results)...); __op->nested_value_complete(); } @@ -500,15 +510,14 @@ namespace exec { void set_error(_Error&& __error) noexcept { auto __op = __op_; stdexec::set_error( - static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_) - , static_cast<_Error&&>(__error)); + static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_), + static_cast<_Error&&>(__error)); __op->nested_value_break(); } void set_stopped() noexcept { auto __op = __op_; - stdexec::set_stopped( - static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_)); + stdexec::set_stopped(static_cast<_NestedValueReceiver&&>(__nested_value_op_->__receiver_)); __op->nested_value_break(); } @@ -532,13 +541,19 @@ namespace exec { __nested_value_op_t __nested_value_op_; __operation_base_interface_t* __op_; - __t(_NestedValueReceiver __rcvr, _NestedValueSender __result, __operation_base_interface_t* __op) + __t( + _NestedValueReceiver __rcvr, + _NestedValueSender __result, + __operation_base_interface_t* __op) noexcept( __nothrow_move_constructible<_NestedValueReceiver> && __nothrow_connectable<_NestedValueSender, __receiver>) : __base_t{static_cast<_NestedValueReceiver&&>(__rcvr)} - , __nested_value_op_{stdexec::connect(static_cast<_NestedValueSender&&>(__result), __receiver{this, __op})} - , __op_{__op} {} + , __nested_value_op_{stdexec::connect( + static_cast<_NestedValueSender&&>(__result), + __receiver{this, __op})} + , __op_{__op} { + } void start() & noexcept { __op_->nested_value_started(); @@ -556,7 +571,8 @@ namespace exec { using sender_concept = stdexec::sender_t; template - using __nested_value_op_t = stdexec::__t<__nested_value_op<_NestedValueSender, _NestedValueReceiverId, _ErrorStorage>>; + using __nested_value_op_t = + stdexec::__t<__nested_value_op<_NestedValueSender, _NestedValueReceiverId, _ErrorStorage>>; template using __receiver = __receive_nested_value<_NestedValueReceiverId, _ErrorStorage>; @@ -566,23 +582,24 @@ namespace exec { template _Self, class... _Env> static auto get_completion_signatures(_Self&&, _Env&&...) noexcept -> stdexec::transform_completion_signatures< - stdexec::completion_signatures_of_t<_NestedValueSender, _Env...>, - stdexec::completion_signatures> { + stdexec::completion_signatures_of_t<_NestedValueSender, _Env...>, + stdexec::completion_signatures + > { return {}; } template _Self, receiver _NestedValueReceiver> static auto connect(_Self&& __self, _NestedValueReceiver&& __rcvr) - noexcept( - __nothrow_constructible_from< - __nested_value_op_t>, - _NestedValueReceiver, - _NestedValueSender, - __operation_base_interface_t*>) - -> __nested_value_op_t> { - return {static_cast<_NestedValueReceiver&&>(__rcvr), - static_cast<_NestedValueSender&&>(__self.__nested_value_), - __self.__op_}; + noexcept(__nothrow_constructible_from< + __nested_value_op_t>, + _NestedValueReceiver, + _NestedValueSender, + __operation_base_interface_t* + >) -> __nested_value_op_t> { + return { + static_cast<_NestedValueReceiver&&>(__rcvr), + static_cast<_NestedValueSender&&>(__self.__nested_value_), + __self.__op_}; } }; }; @@ -600,7 +617,8 @@ namespace exec { // struct __next_operation_interface { - virtual ~__next_operation_interface() {} + virtual ~__next_operation_interface() { + } virtual void nested_sequence_complete() noexcept = 0; virtual void nested_sequence_break() noexcept = 0; }; @@ -614,9 +632,9 @@ namespace exec { __next_operation_base(_NextReceiver __receiver, _OperationBase* __op) noexcept(__nothrow_move_constructible<_NextReceiver>) : __next_operation_interface{} - , __receiver_ {static_cast<_NextReceiver&&>(__receiver)} + , __receiver_{static_cast<_NextReceiver&&>(__receiver)} , __op_{__op} { - } + } void nested_sequence_complete() noexcept override { auto& __op = *__op_; @@ -652,20 +670,19 @@ namespace exec { using __error_storage_t = typename _OperationBase::__error_storage_t; template - using __nested_value_sender_t = stdexec::__t<__nested_value_sender<_NestedValue, __error_storage_t>>; + using __nested_value_sender_t = + stdexec::__t<__nested_value_sender<_NestedValue, __error_storage_t>>; template - auto set_next(_NestedValue&& __nested_value) - noexcept( - __nothrow_callable< - exec::set_next_t, - decltype(__op_->__receiver_), - __nested_value_sender_t<_NestedValue>>) - -> next_sender auto { - return exec::set_next(__op_->__receiver_, - __nested_value_sender_t<_NestedValue>{ - static_cast<_NestedValue&&>(__nested_value), - __op_}); + auto set_next(_NestedValue&& __nested_value) noexcept(__nothrow_callable< + exec::set_next_t, + decltype(__op_->__receiver_), + __nested_value_sender_t<_NestedValue> + >) -> next_sender auto { + return exec::set_next( + __op_->__receiver_, + __nested_value_sender_t<_NestedValue>{ + static_cast<_NestedValue&&>(__nested_value), __op_}); } void set_value() noexcept { @@ -688,11 +705,11 @@ namespace exec { } }; - struct _INVALID_ARGUMENT_TO_MERGE_WITH_REQUIRES_A_SEQUENCE_OF_SEQUENCES_ {}; + struct _INVALID_ARGUMENT_TO_MERGE_WITH_REQUIRES_A_SEQUENCE_OF_SEQUENCES_ { }; template struct __value_completions_error { - template + template using __f = __mexception< _INVALID_ARGUMENT_TO_MERGE_WITH_REQUIRES_A_SEQUENCE_OF_SEQUENCES_, _WITH_SEQUENCE_<_Sequence>, @@ -710,57 +727,59 @@ namespace exec { template struct __arg_of_t { - template - using __f = - stdexec::__meval + using __f = stdexec::__meval< + stdexec::__if_c< sizeof...(_Args) == 1 && (has_sequence_item_types<_Args, _Env...> && ...), stdexec::__q, __value_completions_error<_Sequence, _Sender, _Env...> - >::template __f, _Args...> - ; + >::template __f, + _Args... + >; }; template struct __gather_sequences_t { - template - using __f = - stdexec::__gather_completion_signatures< - stdexec::completion_signatures_of_t<_Sender, _Env...>, - stdexec::set_value_t, - // if set_value - __arg_of_t<_Sequence, _Sender, _Env...>::template __f, - // else remove - stdexec::__mconst>::__f, - // concat to __types result - stdexec::__mtry_q< - stdexec::__mconcat>::template __f> - ::__f - >; - }; + template + using __f = stdexec::__gather_completion_signatures< + stdexec::completion_signatures_of_t<_Sender, _Env...>, + stdexec::set_value_t, + // if set_value + __arg_of_t<_Sequence, _Sender, _Env...>::template __f, + // else remove + stdexec::__mconst>::__f, + // concat to __types result + stdexec::__mtry_q>::template __f>::__f + >; + }; template - using __nested_sequences_from_item_type_t = - stdexec::__mapply< - stdexec::__if_c< - stdexec::__mvalid + using __nested_sequences_from_item_type_t = stdexec::__mapply< + stdexec::__if_c< + stdexec::__mvalid && stdexec::__mvalid<__gather_sequences_t<_Sequence, _Sender, _Env...>::template __f>, - __gather_sequences_t<_Sequence, _Sender, _Env...>, - __value_completions_error<_Sequence, _Sender, _Env...>>, - stdexec::__completion_signatures_of_t<_Sender, _Env...>>; + __gather_sequences_t<_Sequence, _Sender, _Env...>, + __value_completions_error<_Sequence, _Sender, _Env...> + >, + stdexec::__completion_signatures_of_t<_Sender, _Env...> + >; template struct __nested_sequences_t { template using __f = stdexec::__mapply< - stdexec::__munique>, + stdexec::__munique>, stdexec::__minvoke< stdexec::__mconcat>, - __nested_sequences_from_item_type_t<_Sequence, _Senders, _Env...>...>>; + __nested_sequences_from_item_type_t<_Sequence, _Senders, _Env...>... + > + >; }; template - using __nested_sequences = __mapply<__nested_sequences_t<_Sequence, _Env...>, __item_types_of_t<_Sequence, _Env...>>; + using __nested_sequences = + __mapply<__nested_sequences_t<_Sequence, _Env...>, __item_types_of_t<_Sequence, _Env...>>; // // __all_nested_values extracts the types of all the nested value senders. @@ -771,29 +790,33 @@ namespace exec { template using __f = stdexec::__minvoke< - stdexec::__mconcat>, - __item_types_of_t<_Sequences, _Env...>...>; + stdexec::__mconcat>, + __item_types_of_t<_Sequences, _Env...>... + >; }; template - using __all_nested_values = __mapply<__all_nested_values_t<_Env...>, __nested_sequences<_Sequence, _Env...>>; + using __all_nested_values = + __mapply<__all_nested_values_t<_Env...>, __nested_sequences<_Sequence, _Env...>>; // // __error_types extracts the types of all the errors emitted by all the senders in the list. // - template> + template > struct __error_types_t { - template + template using __f = stdexec::error_types_of_t<_Sender, _Env, stdexec::__types>; }; - template + template using __error_types = stdexec::__mapply< - stdexec::__mtransform< - __error_types_t<_Env...>, - stdexec::__mconcat>>, - _Senders>; + stdexec::__mtransform< + __error_types_t<_Env...>, + stdexec::__mconcat> + >, + _Senders + >; // // __errors extracts the types of all the errors emitted by: @@ -804,16 +827,16 @@ namespace exec { // output sequence. // - template + template using __errors = stdexec::__minvoke< - stdexec::__mconcat>, - // always include std::exception_ptr - stdexec::__types, - // include errors from senders of the nested sequences - __error_types<__item_types_of_t<_Sequence, _Env...>, _Env...>, - // include errors from the nested sequences - __error_types<__merge_each::__compute::__nested_sequences<_Sequence, _Env...>, _Env...> - >; + stdexec::__mconcat>, + // always include std::exception_ptr + stdexec::__types, + // include errors from senders of the nested sequences + __error_types<__item_types_of_t<_Sequence, _Env...>, _Env...>, + // include errors from the nested sequences + __error_types<__merge_each::__compute::__nested_sequences<_Sequence, _Env...>, _Env...> + >; // // __error_variant makes a variant type of all the errors @@ -822,18 +845,18 @@ namespace exec { // all active operations are completed. // - template - using __error_variant = stdexec::__mapply< - __q, - __errors<_Sequence, _Env...>>; + template + using __error_variant = + stdexec::__mapply<__q, __errors<_Sequence, _Env...>>; // // __nested_values extracts the types of all the nested value senders and // builds the item_types list for the merge_each sequence sender. // - template - using __nested_value_sender_t = stdexec::__t<__nested_value_sender<_NestedValueSender, _ErrorStorage>>; + template + using __nested_value_sender_t = + stdexec::__t<__nested_value_sender<_NestedValueSender, _ErrorStorage>>; template struct __nested_values_t { @@ -841,38 +864,47 @@ namespace exec { template using __f = stdexec::__mapply< stdexec::__munique>, - stdexec::__types< - __nested_value_sender_t<_AllItems, _ErrorStorage>..., - __t<__error_sender<_ErrorStorage>>>>; + stdexec::__types< + __nested_value_sender_t<_AllItems, _ErrorStorage>..., + __t<__error_sender<_ErrorStorage>> + > + >; }; template using __nested_values = stdexec::__mapply< __nested_values_t<__error_variant<_Sequence, _Env...>, _Env...>, - __all_nested_values<_Sequence, _Env...>>; + __all_nested_values<_Sequence, _Env...> + >; // // __nested_sequence_ops_variant makes a variant that contains the // types of all the nested sequence operations. // - template + template struct __nested_sequence_op_t { - template + template using __f = subscribe_result_t<_Sequence, __receive_nested_values<_OperationBase>>; }; - template - using __operation_base_t = __operation_base<_Receiver, __error_variant<_Sequence, __env_with_inplace_stop_token_result_t>>>; + template + using __operation_base_t = __operation_base< + _Receiver, + __error_variant<_Sequence, __env_with_inplace_stop_token_result_t>> + >; - template + template using __nested_sequence_ops_variant = stdexec::__mapply< stdexec::__mtransform< __nested_sequence_op_t<__operation_base_t<_Sequence, _Receiver>>, - stdexec::__qq>, - __merge_each::__compute::__nested_sequences<_Sequence, __env_with_inplace_stop_token_result_t>> + stdexec::__qq + >, + __merge_each::__compute::__nested_sequences< + _Sequence, + __env_with_inplace_stop_token_result_t> + > >; - }; // @@ -896,25 +928,30 @@ namespace exec { template auto set_value(_NestedSequence&& __sequence) noexcept { - using __nested_op_t = subscribe_result_t<_NestedSequence, __receive_nested_values<_OperationBase>>; + using __nested_op_t = + subscribe_result_t<_NestedSequence, __receive_nested_values<_OperationBase>>; if constexpr ( __nothrow_subscribable<_NestedSequence, __receive_nested_values<_OperationBase>> && stdexec::__nothrow_constructible_from<_NestedSeqOp, __nested_op_t>) { auto& __nested_seq_op = __next_seq_op_->__nested_seq_op_.emplace_from( - [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { - return subscribe(static_cast<_NestedSequence&&>(__sequence), static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); - }, - static_cast<_NestedSequence&&>(__sequence), - __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); + [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { + return subscribe( + static_cast<_NestedSequence&&>(__sequence), + static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); + }, + static_cast<_NestedSequence&&>(__sequence), + __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); stdexec::start(__nested_seq_op); } else { STDEXEC_TRY { auto& __nested_seq_op = __next_seq_op_->__nested_seq_op_.emplace_from( - [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { - return subscribe(static_cast<_NestedSequence&&>(__sequence), static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); - }, - static_cast<_NestedSequence&&>(__sequence), - __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); + [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { + return subscribe( + static_cast<_NestedSequence&&>(__sequence), + static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); + }, + static_cast<_NestedSequence&&>(__sequence), + __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); stdexec::start(__nested_seq_op); } STDEXEC_CATCH_ALL { @@ -940,7 +977,12 @@ namespace exec { } }; - template + template < + class _NestedSequenceSender, + class _NextReceiverId, + class _OperationBase, + class _NestedSeqOp + > struct __next_sequence_op { using _NextReceiver = stdexec::__t<_NextReceiverId>; using __base_t = __next_operation_base<_NextReceiver, _OperationBase, _NestedSeqOp>; @@ -958,7 +1000,10 @@ namespace exec { && __nothrow_connectable<_NestedSequenceSender, __receiver>) : __base_t{static_cast<_NextReceiver&&>(__rcvr), __op} , __op_(__op) - , __nested_sequence_op_{stdexec::connect(static_cast<_NestedSequenceSender&&>(__nested_sequence), __receiver{this, __op_})} {} + , __nested_sequence_op_{stdexec::connect( + static_cast<_NestedSequenceSender&&>(__nested_sequence), + __receiver{this, __op_})} { + } void start() & noexcept { __op_->nested_sequence_started(); @@ -975,7 +1020,9 @@ namespace exec { using sender_concept = stdexec::sender_t; template - using __next_sequence_op_t = stdexec::__t<__next_sequence_op<_NestedSequenceSender, _NextReceiverId, _OperationBase, _NestedSeqOp>>; + using __next_sequence_op_t = stdexec::__t< + __next_sequence_op<_NestedSequenceSender, _NextReceiverId, _OperationBase, _NestedSeqOp> + >; _OperationBase* __op_; _NestedSequenceSender __nested_sequence_; @@ -988,16 +1035,16 @@ namespace exec { template _Self, receiver _NextReceiver> static auto connect(_Self&& __self, _NextReceiver&& __rcvr) - noexcept( - __nothrow_constructible_from< - __next_sequence_op_t>, - _NextReceiver, - _OperationBase*, - _NestedSequenceSender>) - -> __next_sequence_op_t> { - return {static_cast<_NextReceiver&&>(__rcvr), - __self.__op_, - static_cast<_NestedSequenceSender&&>(__self.__nested_sequence_)}; + noexcept(__nothrow_constructible_from< + __next_sequence_op_t>, + _NextReceiver, + _OperationBase*, + _NestedSequenceSender + >) -> __next_sequence_op_t> { + return { + static_cast<_NextReceiver&&>(__rcvr), + __self.__op_, + static_cast<_NestedSequenceSender&&>(__self.__nested_sequence_)}; } }; }; @@ -1015,19 +1062,20 @@ namespace exec { using receiver_concept = receiver_t; template - using __next_sequence_sender_t = stdexec::__t<__next_sequence_sender<_NestedSequenceSender, _OperationBase, _NestedSeqOp>>; + using __next_sequence_sender_t = + stdexec::__t<__next_sequence_sender<_NestedSequenceSender, _OperationBase, _NestedSeqOp>>; _OperationBase* __op_; template auto set_next(_NestedSequenceSender&& __nested_sequence) - noexcept( - __nothrow_constructible_from< - __next_sequence_sender_t<_NestedSequenceSender>, - _OperationBase*, - _NestedSequenceSender>) - -> next_sender auto { - return __next_sequence_sender_t<_NestedSequenceSender>{__op_, static_cast<_NestedSequenceSender>(__nested_sequence)}; + noexcept(__nothrow_constructible_from< + __next_sequence_sender_t<_NestedSequenceSender>, + _OperationBase*, + _NestedSequenceSender + >) -> next_sender auto { + return __next_sequence_sender_t<_NestedSequenceSender>{ + __op_, static_cast<_NestedSequenceSender>(__nested_sequence)}; } void set_value() noexcept { @@ -1054,7 +1102,10 @@ namespace exec { template struct __operation { using _Receiver = stdexec::__t<_ReceiverId>; - using __error_storage_t = __compute::__error_variant<_Sequence, __env_with_inplace_stop_token_result_t>>; + using __error_storage_t = __compute::__error_variant< + _Sequence, + __env_with_inplace_stop_token_result_t> + >; using __base_t = __operation_base<_Receiver, __error_storage_t>; struct __t : __base_t { using __id = __operation; @@ -1066,12 +1117,11 @@ namespace exec { using __op_t = subscribe_result_t<_Sequence, __receiver>; __op_t __op_; - __t(_Receiver __rcvr, _Sequence __sequence) - noexcept( - __nothrow_subscribable<_Sequence, __receiver> - && __nothrow_move_constructible<_Receiver>) + __t(_Receiver __rcvr, _Sequence __sequence) noexcept( + __nothrow_subscribable<_Sequence, __receiver> && __nothrow_move_constructible<_Receiver>) : __base_t{static_cast<_Receiver&&>(__rcvr)} - ,__op_{subscribe(static_cast<_Sequence&&>(__sequence), __receiver{this})} {} + , __op_{subscribe(static_cast<_Sequence&&>(__sequence), __receiver{this})} { + } void start() & noexcept { this->__nested_stop_.register_token(this->__receiver_); @@ -1093,15 +1143,12 @@ namespace exec { template auto operator()(__ignore, __ignore, _Sequence __sequence) - noexcept( - __nothrow_constructible_from< - __t<__operation<__id<_Receiver>, _Sequence>>, - _Receiver, - _Sequence>) - -> __t<__operation<__id<_Receiver>, _Sequence>> { - return { - static_cast<_Receiver&&>(__rcvr_), - static_cast<_Sequence&&>(__sequence)}; + noexcept(__nothrow_constructible_from< + __t<__operation<__id<_Receiver>, _Sequence>>, + _Receiver, + _Sequence + >) -> __t<__operation<__id<_Receiver>, _Sequence>> { + return {static_cast<_Receiver&&>(__rcvr_), static_cast<_Sequence&&>(__sequence)}; } }; @@ -1133,19 +1180,18 @@ namespace exec { struct merge_each_t { template - auto operator()(_Sequence&& __sequence) const - noexcept(__nothrow_decay_copyable<_Sequence>) + auto operator()(_Sequence&& __sequence) const noexcept(__nothrow_decay_copyable<_Sequence>) -> __well_formed_sequence_sender auto { - return make_sequence_expr( - __(), static_cast<_Sequence&&>(__sequence)); + return make_sequence_expr(__(), static_cast<_Sequence&&>(__sequence)); } template _Self, class... _Env> static auto get_item_types(_Self&&, _Env&&...) noexcept { - return __minvoke< - __mtry_catch<__q<__compute::__nested_values>, __q<__argument_error_t>>, - __child_of<_Self>, - __env_with_inplace_stop_token_result_t<_Env...>>(); + return __minvoke< + __mtry_catch<__q<__compute::__nested_values>, __q<__argument_error_t>>, + __child_of<_Self>, + __env_with_inplace_stop_token_result_t<_Env...> + >(); } template @@ -1161,24 +1207,27 @@ namespace exec { }; template - using __completions = __mapply<__completions_t<_Self, _Env...>, __compute::__nested_sequences<__child_of<_Self>, _Env...>>; + using __completions = __mapply< + __completions_t<_Self, _Env...>, + __compute::__nested_sequences<__child_of<_Self>, _Env...> + >; template _Self, class... _Env> static auto get_completion_signatures(_Self&&, _Env&&...) noexcept { - return __minvoke<__mtry_catch<__q<__completions>, __q<__argument_error_t>>, - _Self, - __env_with_inplace_stop_token_result_t<_Env...>>{}; + return __minvoke< + __mtry_catch<__q<__completions>, __q<__argument_error_t>>, + _Self, + __env_with_inplace_stop_token_result_t<_Env...> + >{}; } static constexpr auto subscribe = [](_Sequence&& __sndr, _Receiver __rcvr) noexcept( __nothrow_callable<__sexpr_apply_t, _Sequence, __subscribe_fn<_Receiver>>) - -> __sexpr_apply_result_t<_Sequence, __subscribe_fn<_Receiver>> - { + -> __sexpr_apply_result_t<_Sequence, __subscribe_fn<_Receiver>> { static_assert(sender_expr_for<_Sequence, merge_each_t>); return __sexpr_apply(static_cast<_Sequence&&>(__sndr), __subscribe_fn<_Receiver>{__rcvr}); }; - }; } // namespace __merge_each diff --git a/include/exec/sequence/notification.hpp b/include/exec/sequence/notification.hpp index 793f0bd4a..f2bf81cda 100644 --- a/include/exec/sequence/notification.hpp +++ b/include/exec/sequence/notification.hpp @@ -78,10 +78,10 @@ namespace exec { std::variant >; - template + template struct __notification_sender; - template + template struct notification_t { using __notification_t = __notification_storage_t<_CompletionSignatures>; using __notification_sender_t = stdexec::__t<__notification_sender<_CompletionSignatures>>; @@ -89,96 +89,107 @@ namespace exec { __notification_t __notification_{}; template - using __tag_of_t = stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Notification)>; - - template - notification_t(_Tag __tag, _Args&&... __args) - noexcept( - noexcept( - __notification_.template emplace<__decayed_tuple<_Tag, STDEXEC_REMOVE_REFERENCE(_Args)...>>( - __tag, - static_cast<_Args&&>(__args)...))) { - __notification_.template emplace<__decayed_tuple<_Tag, STDEXEC_REMOVE_REFERENCE(_Args)...>>(__tag, static_cast<_Args&&>(__args)...); - } + using __tag_of_t = + stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Notification)>; + + template + notification_t(_Tag __tag, _Args&&... __args) noexcept(noexcept( + __notification_.template emplace<__decayed_tuple<_Tag, STDEXEC_REMOVE_REFERENCE(_Args)...>>( + __tag, + static_cast<_Args&&>(__args)...))) { + __notification_.template emplace<__decayed_tuple<_Tag, STDEXEC_REMOVE_REFERENCE(_Args)...>>( + __tag, static_cast<_Args&&>(__args)...); + } - template + template auto visit(_Fn&& __fn) const noexcept { return std::visit( [&__fn](auto&& __tuple) noexcept { - return __tuple.apply([&__fn](auto __tag, auto&&... __args) noexcept { - return static_cast<_Fn&&>(__fn)(__tag, __args...); - } - , __tuple); - } - , __notification_); + return __tuple.apply( + [&__fn](auto __tag, auto&&... __args) noexcept { + return static_cast<_Fn&&>(__fn)(__tag, __args...); + }, + __tuple); + }, + __notification_); } - template + template void visit_receiver(_Receiver&& __receiver) noexcept { std::visit( [&__receiver](auto&& __tuple) noexcept { - __tuple.apply([&__receiver](auto __tag, auto&&... __args) noexcept { - __tag(static_cast<_Receiver&&>(__receiver), static_cast(__args)...); - } - , static_cast(__tuple)); - } - , static_cast<__notification_t&&>(__notification_)); + __tuple.apply( + [&__receiver](auto __tag, auto&&... __args) noexcept { + __tag( + static_cast<_Receiver&&>(__receiver), static_cast(__args)...); + }, + static_cast(__tuple)); + }, + static_cast<__notification_t&&>(__notification_)); } auto visit_sender() noexcept -> __notification_sender_t; - [[nodiscard]] bool value() const noexcept { + [[nodiscard]] + bool value() const noexcept { return std::visit( - [](const _Tuple&) noexcept { - return stdexec::__decays_to>; - } - , __notification_); + [](const _Tuple&) noexcept { + return stdexec::__decays_to>; + }, + __notification_); } - [[nodiscard]] bool error() const noexcept { + [[nodiscard]] + bool error() const noexcept { return std::visit( - [](const _Tuple&) noexcept { - return stdexec::__decays_to>; - } - , __notification_); + [](const _Tuple&) noexcept { + return stdexec::__decays_to>; + }, + __notification_); } - [[nodiscard]] bool stopped() const noexcept { + [[nodiscard]] + bool stopped() const noexcept { return std::visit( - [](const _Tuple&) noexcept { - return stdexec::__decays_to>; - } - , __notification_); + [](const _Tuple&) noexcept { + return stdexec::__decays_to>; + }, + __notification_); } - friend auto operator==( - const notification_t& __lhs, - const notification_t& __rhs) noexcept -> bool { - return std::visit( - [](const _Lhs& __lhs, const _Rhs& __rhs) noexcept { - if constexpr ( - !std::same_as<__tag_of_t<_Lhs>, __tag_of_t<_Rhs>> - || stdexec::__v> != stdexec::__v>) { - return false; - } else { - return __lhs.apply([&__rhs](_LTag, const _LArgs&... __l_args){ - return __rhs.apply([&](_RTag, const _RArgs&... __r_args){ - if constexpr ((std::equality_comparable_with && ... && true)) { - return ((__l_args == __r_args) && ... && true); - } else { - return false; - } - } - , __rhs); - } - , __lhs); - } + friend auto + operator==(const notification_t& __lhs, const notification_t& __rhs) noexcept -> bool { + return std::visit( + [](const _Lhs& __lhs, const _Rhs& __rhs) noexcept { + if constexpr ( + !std::same_as<__tag_of_t<_Lhs>, __tag_of_t<_Rhs>> + || stdexec::__v> + != stdexec::__v>) { + return false; + } else { + return __lhs.apply( + [&__rhs](_LTag, const _LArgs&... __l_args) { + return __rhs.apply( + [&](_RTag, const _RArgs&... __r_args) { + if constexpr ((std::equality_comparable_with + && ... && true)) { + return ((__l_args == __r_args) && ... && true); + } else { + return false; + } + }, + __rhs); + }, + __lhs); } - , __lhs.__notification_, __rhs.__notification_); + }, + __lhs.__notification_, + __rhs.__notification_); } friend std::string to_string(const notification_t& __self) noexcept { using std::to_string; - return __self.visit([](auto __tag, const auto&... __args){ - int count = 0; - return to_string(__tag) + "(" + (((count++ > 0 ? ", " : "") + to_string(__args)) + ... + std::string{}) + ")"; - }); + return __self.visit([](auto __tag, const auto&... __args) { + int count = 0; + return to_string(__tag) + "(" + + (((count++ > 0 ? ", " : "") + to_string(__args)) + ... + std::string{}) + ")"; + }); } }; @@ -199,42 +210,42 @@ namespace exec { }; }; - template + template struct __notification_sender { using __notification_t = notification_t<_CompletionSignatures>; struct __t { using __id = __notification_sender; using sender_concept = stdexec::sender_t; - template - using __notification_op_t = stdexec::__t<__notification_op<_ReceiverId, _CompletionSignatures>>; + template + using __notification_op_t = + stdexec::__t<__notification_op<_ReceiverId, _CompletionSignatures>>; __notification_t* __notification_; template _Self, class... _Env> - static auto get_completion_signatures(_Self&&, _Env&&...) noexcept - -> _CompletionSignatures { + static auto + get_completion_signatures(_Self&&, _Env&&...) noexcept -> _CompletionSignatures { return {}; } template _Self, receiver _Receiver> static auto connect(_Self&& __self, _Receiver&& __rcvr) noexcept(__nothrow_move_constructible<_Receiver>) - -> __notification_op_t> { - return {static_cast<_Receiver&&>(__rcvr), - __self.__notification_}; + -> __notification_op_t> { + return {static_cast<_Receiver&&>(__rcvr), __self.__notification_}; } }; }; - template + template auto notification_t<_CompletionSignatures>::visit_sender() noexcept -> notification_t<_CompletionSignatures>::__notification_sender_t { return {this}; } } // namespace __notification - template + template using notification_t = __notification::notification_t<_CompletionSignatures>; namespace __notification { diff --git a/include/exec/sequence/test_scheduler.hpp b/include/exec/sequence/test_scheduler.hpp index 960ab0a77..cb18d9079 100644 --- a/include/exec/sequence/test_scheduler.hpp +++ b/include/exec/sequence/test_scheduler.hpp @@ -111,43 +111,45 @@ namespace exec { // fast in real-time // - struct test_clock - { - using duration = std::chrono::milliseconds; - using rep = duration::rep; - using period = duration::period; - using time_point = std::chrono::time_point; - [[maybe_unused]] static const bool is_steady = false; - - const test_clock_context* __context_; - - [[maybe_unused, nodiscard]] time_point now() const noexcept; + struct test_clock { + using duration = std::chrono::milliseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + [[maybe_unused]] + static const bool is_steady = false; + + const test_clock_context* __context_; + + [[maybe_unused, nodiscard]] + time_point now() const noexcept; }; - struct test_clock_context - { - using duration = typename test_clock::duration; - using rep = duration::rep; - using period = duration::period; - using time_point = typename test_clock::time_point; - [[maybe_unused]] static const bool is_steady = test_clock::is_steady; + struct test_clock_context { + using duration = typename test_clock::duration; + using rep = duration::rep; + using period = duration::period; + using time_point = typename test_clock::time_point; + [[maybe_unused]] + static const bool is_steady = test_clock::is_steady; - time_point __now_{}; + time_point __now_{}; - [[maybe_unused, nodiscard]] time_point now() const noexcept { - return __now_; - } + [[maybe_unused, nodiscard]] + time_point now() const noexcept { + return __now_; + } - auto advance_now_to(time_point __new_now) noexcept -> time_point { - time_point __old_now = __now_; - __now_ = __new_now; - return __old_now; - } - auto advance_now_by(duration __by) noexcept -> time_point { - time_point __old_now = __now_; - __now_ += __by; - return __old_now; - } + auto advance_now_to(time_point __new_now) noexcept -> time_point { + time_point __old_now = __now_; + __now_ = __new_now; + return __old_now; + } + auto advance_now_by(duration __by) noexcept -> time_point { + time_point __old_now = __now_; + __now_ += __by; + return __old_now; + } }; inline typename test_clock::time_point test_clock::now() const noexcept { @@ -262,17 +264,17 @@ namespace exec { auto now() const noexcept -> time_point; // parse a marble diagram into a set of marbles - template + template auto get_marbles_from(stdexec::__mstring<_Len> __diagram) noexcept -> std::vector> { - return exec::get_marbles_from(get_clock(), __diagram); - } + return exec::get_marbles_from(get_clock(), __diagram); + } // record the results of a sequence-sender as a set of marbles - template + template auto get_marbles_from( - _Sequence&& __sequence - , typename test_clock::duration __stop_after = std::chrono::milliseconds(1000)) noexcept + _Sequence&& __sequence, + typename test_clock::duration __stop_after = std::chrono::milliseconds(1000)) noexcept -> std::vector>; @@ -283,7 +285,7 @@ namespace exec { // parse a marble diagram into a set of marbles and return a sequence // sender that will emit those marbles - template + template auto get_marble_sequence_from(stdexec::__mstring<_Len> __diagram) noexcept -> _tst_sched::__test_sequence; @@ -324,7 +326,7 @@ namespace exec { std::ptrdiff_t expected = 0; while (!n_submissions_in_flight_ .compare_exchange_weak(expected, context_closed, std::memory_order_relaxed) - && expected > 0) { + && expected > 0) { expected = 0; } task_type* op = heap_.front(); @@ -409,15 +411,11 @@ namespace exec { namespace _tst_sched { template - class test_schedule_at_op::__t - : _tst_sched::test_schedule_operation_base { + class test_schedule_at_op::__t : _tst_sched::test_schedule_operation_base { public: using __id = test_schedule_at_op; - __t( - test_context& context, - test_clock::time_point time_point, - Receiver receiver) noexcept + __t(test_context& context, test_clock::time_point time_point, Receiver receiver) noexcept : _tst_sched::test_schedule_operation_base{ time_point, [](_tst_sched::test_operation_base* op) noexcept { @@ -506,9 +504,7 @@ namespace exec { using completion_signatures = stdexec::completion_signatures; - schedule_at_sender( - test_context& context, - test_clock::time_point time_point) noexcept + schedule_at_sender(test_context& context, test_clock::time_point time_point) noexcept : context_{&context} , time_point_{time_point} { } @@ -516,8 +512,7 @@ namespace exec { [[nodiscard]] auto get_env() const noexcept { return stdexec::prop{ - stdexec::get_completion_scheduler, - test_scheduler{*context_}}; + stdexec::get_completion_scheduler, test_scheduler{*context_}}; } template @@ -594,16 +589,19 @@ namespace exec { } using env_t = decltype(stdexec::__env::__join( - stdexec::prop{stdexec::get_scheduler, stdexec::__declval()}, - stdexec::prop{stdexec::get_stop_token, stdexec::__declval()})); - [[nodiscard]] auto get_env() const noexcept -> env_t { + stdexec::prop{stdexec::get_scheduler, stdexec::__declval()}, + stdexec::prop{ + stdexec::get_stop_token, + stdexec::__declval()})); + [[nodiscard]] + auto get_env() const noexcept -> env_t { return stdexec::__env::__join( stdexec::prop{stdexec::get_scheduler, __context_->get_scheduler()}, stdexec::prop{stdexec::get_stop_token, __stop_source_->get_token()}); } }; - template + template struct __next_receiver { using __t = __next_receiver; using __id = __next_receiver; @@ -615,13 +613,14 @@ namespace exec { void set_value() noexcept { } - template + template void set_error(_Error&&) noexcept { } void set_stopped() noexcept { } - [[nodiscard]] auto get_env() const noexcept -> stdexec::env_of_t<_Receiver> { + [[nodiscard]] + auto get_env() const noexcept -> stdexec::env_of_t<_Receiver> { return stdexec::get_env(*__receiver_); } }; @@ -630,80 +629,87 @@ namespace exec { // __proxy.. are a hammer to workaround a type handling bug in clang 19 // - template + template struct __proxy_fn { - template + template requires stdexec::__decays_to_derived_from<_Base, _Derived> auto operator()(_Derived&& __derived, _Args&&... __args) const - noexcept( - stdexec::__nothrow_callable, _Args...>) - -> stdexec::__call_result_t, _Args...> { + noexcept(stdexec::__nothrow_callable< + decltype(_Fn), + stdexec::__copy_cvref_t<_Derived, _Base>, + _Args... + >) + -> stdexec::__call_result_t< + decltype(_Fn), + stdexec::__copy_cvref_t<_Derived, _Base>, + _Args... + > { return _Fn( - static_cast&&>(__derived) - , static_cast<_Args&&>(__args)...); + static_cast&&>(__derived), + static_cast<_Args&&>(__args)...); }; }; - template + template struct __proxy_operation { using _Sender = stdexec::__t<_SenderId>; static_assert(stdexec::__callable); using __operation_t = stdexec::connect_result_t<_Sender, _Receiver>; - using __t [[maybe_unused]] = __proxy_operation; + using __t [[maybe_unused]] = __proxy_operation; using __id [[maybe_unused]] = __proxy_operation; __operation_t __op_; - __proxy_operation([[maybe_unused]] _Sender&& __sender, [[maybe_unused]] _Receiver&& __receiver) - noexcept(stdexec::__nothrow_connectable<_Sender, _Receiver>) - : __op_{ - stdexec::connect( - static_cast<_Sender&&>(__sender) - , static_cast<_Receiver&&>(__receiver)) - } - {} + __proxy_operation( + [[maybe_unused]] _Sender&& __sender, + [[maybe_unused]] + _Receiver&& __receiver) noexcept(stdexec::__nothrow_connectable<_Sender, _Receiver>) + : __op_{stdexec::connect( + static_cast<_Sender&&>(__sender), + static_cast<_Receiver&&>(__receiver))} { + } void start() noexcept { stdexec::start(__op_); } }; - template + template struct __proxy_sender { using _Sender = stdexec::__t<_SenderId>; - using __t [[maybe_unused]] = __proxy_sender; + using __t [[maybe_unused]] = __proxy_sender; using __id [[maybe_unused]] = __proxy_sender; using sender_concept = typename _Sender::sender_concept; _Sender __sender_; - explicit __proxy_sender(_Sender __sender) : __sender_{__sender} {} + explicit __proxy_sender(_Sender __sender) + : __sender_{__sender} { + } static constexpr auto get_completion_signatures = - [] _Self, class... _Env> - (_Self&&, _Env&&...) - noexcept(stdexec::__nothrow_callable< - stdexec::get_completion_signatures_t, - stdexec::__copy_cvref_t<_Self, _Sender> - , _Env...>) - -> stdexec::completion_signatures_of_t< - stdexec::__copy_cvref_t<_Self, _Sender> - , _Env...> { - return {}; - }; + [] _Self, class... _Env>(_Self&&, _Env&&...) noexcept( + stdexec::__nothrow_callable< + stdexec::get_completion_signatures_t, + stdexec::__copy_cvref_t<_Self, _Sender>, + _Env... + >) + -> stdexec::completion_signatures_of_t, _Env...> { + return {}; + }; - template + template using __operation_t = __proxy_operation<_SenderId, _Receiver>; static constexpr auto connect = - [] _Self, class _Receiver> - (_Self&& __self, _Receiver&& __receiver) - noexcept(stdexec::__nothrow_connectable<_Sender, _Receiver>) - -> __operation_t<_Receiver> { - return __operation_t<_Receiver>{ - static_cast&&>(__self.__sender_) - , static_cast<_Receiver&&>(__receiver)}; - }; + [] _Self, class _Receiver>( + _Self&& __self, + _Receiver&& __receiver) noexcept(stdexec::__nothrow_connectable<_Sender, _Receiver>) + -> __operation_t<_Receiver> { + return __operation_t<_Receiver>{ + static_cast&&>(__self.__sender_), + static_cast<_Receiver&&>(__receiver)}; + }; }; // @@ -722,7 +728,7 @@ namespace exec { } }; - template + template struct __test_sequence_operation : __test_sequence_operation_base { using _Receiver = stdexec::__t<_ReceiverId>; @@ -746,66 +752,68 @@ namespace exec { __self_->request_stop(); if (__self.__active_ops_ == 0) { if (!__self.__end_marble_) { - __self.__requested_stop_marble_.visit_sequence_receiver(static_cast<_Receiver&&>(__self.__receiver_)); + __self.__requested_stop_marble_ + .visit_sequence_receiver(static_cast<_Receiver&&>(__self.__receiver_)); } } } }; using __stop_callback_t = stdexec::stop_callback_for_t< - stdexec::stop_token_of_t> - , __stop_callback_fn_t>; + stdexec::stop_token_of_t>, + __stop_callback_fn_t + >; std::optional<__stop_callback_t> __on_stop_; - template - static auto __schedule_at(__test_sequence_operation& __self, __marble_t& __marble, _Completion&& __completion) noexcept { + template + static auto __schedule_at( + __test_sequence_operation& __self, + __marble_t& __marble, + _Completion&& __completion) noexcept { return stdexec::write_env( - // schedule the marble completion at the specified frame - exec::sequence( - exec::schedule_at(__self.__context_->get_scheduler(), __marble.frame()) - , static_cast<_Completion&&>(__completion)), - stdexec::prop{stdexec::get_stop_token, __self.__stop_source_.get_token()}) - | exec::finally( - stdexec::just() - | stdexec::then([&__self, &__marble]() noexcept { - // after each completion, update the __test_sequence_operation state - STDEXEC_ASSERT(__self.__active_ops_ > 0); - if ( - __marble.error_notification() - || __marble.stopped_notification() - || __marble.sequence_error() - || __marble.sequence_stopped() - || __marble.sequence_end()) { - // these marbles trigger the whole sequence - // to complete with no more items - if (!__self.__end_marble_) { - // set as the end marble - // this determines the signal that will be used to - // complete the sequence after all remaining active - // operations have completed - __self.__end_marble_ = &__marble; - } - // cancel all pending ops - __self.request_stop(); - } - if (--__self.__active_ops_ == 0) { - // all ops are complete, - if (!!__self.__end_marble_) { - __self.__on_stop_.reset(); - __self.__end_marble_->visit_sequence_receiver(static_cast<_Receiver&&>(__self.__receiver_)); - } - // else this sequence never completes - - // this sequence must be stopped externally - } - })); + // schedule the marble completion at the specified frame + exec::sequence( + exec::schedule_at(__self.__context_->get_scheduler(), __marble.frame()), + static_cast<_Completion&&>(__completion)), + stdexec::prop{stdexec::get_stop_token, __self.__stop_source_.get_token()}) + | exec::finally(stdexec::just() | stdexec::then([&__self, &__marble]() noexcept { + // after each completion, update the __test_sequence_operation state + STDEXEC_ASSERT(__self.__active_ops_ > 0); + if ( + __marble.error_notification() || __marble.stopped_notification() + || __marble.sequence_error() || __marble.sequence_stopped() + || __marble.sequence_end()) { + // these marbles trigger the whole sequence + // to complete with no more items + if (!__self.__end_marble_) { + // set as the end marble + // this determines the signal that will be used to + // complete the sequence after all remaining active + // operations have completed + __self.__end_marble_ = &__marble; + } + // cancel all pending ops + __self.request_stop(); + } + if (--__self.__active_ops_ == 0) { + // all ops are complete, + if (!!__self.__end_marble_) { + __self.__on_stop_.reset(); + __self.__end_marble_->visit_sequence_receiver( + static_cast<_Receiver&&>(__self.__receiver_)); + } + // else this sequence never completes - + // this sequence must be stopped externally + } + })); } using __receiver_t = __next_receiver<_ReceiverId>; - static auto __schedule_marble(__test_sequence_operation& __self, __marble_t& __marble) noexcept { - using __next_sender_t = decltype( - __schedule_at(__self, __marble, exec::set_next(__self.__receiver_, __marble.visit_sender()))); - using __end_sender_t = decltype( - __schedule_at(__self, __marble, stdexec::just())); + static auto + __schedule_marble(__test_sequence_operation& __self, __marble_t& __marble) noexcept { + using __next_sender_t = decltype(__schedule_at( + __self, __marble, exec::set_next(__self.__receiver_, __marble.visit_sender()))); + using __end_sender_t = decltype(__schedule_at(__self, __marble, stdexec::just())); struct __next_sender_id { using __t = __next_sender_t; }; @@ -822,26 +830,27 @@ namespace exec { using __result_t = variant_sender<__next_sender_proxy_t, __end_sender_proxy_t>; if (__marble.__notification_.has_value()) { - return __result_t{__next_sender_proxy_t{{ - __schedule_at(__self, __marble, exec::set_next(__self.__receiver_, __marble.visit_sender()))}}}; + return __result_t{__next_sender_proxy_t{{__schedule_at( + __self, __marble, exec::set_next(__self.__receiver_, __marble.visit_sender()))}}}; } else { - return __result_t{__end_sender_proxy_t{{ - __schedule_at(__self, __marble, stdexec::just())}}}; + return __result_t{ + __end_sender_proxy_t{{__schedule_at(__self, __marble, stdexec::just())}}}; } } using __scheduled_marble_t = stdexec::__call_result_t< - decltype(&__schedule_marble) - , __test_sequence_operation& - , __marble_t&>; + decltype(&__schedule_marble), + __test_sequence_operation&, + __marble_t& + >; using __marble_op_t = stdexec::connect_result_t<__scheduled_marble_t, __receiver_t>; std::deque> __marble_ops_{}; __test_sequence_operation( - std::vector<__marble_t> __marbles - , test_context* __context - , _Receiver&& __receiver) noexcept + std::vector<__marble_t> __marbles, + test_context* __context, + _Receiver&& __receiver) noexcept : __test_sequence_operation_base{__context} , __marbles_{static_cast&&>(__marbles)} , __receiver_{static_cast<_Receiver&&>(__receiver)} { @@ -849,21 +858,17 @@ namespace exec { void start() noexcept { __on_stop_.emplace( - stdexec::get_stop_token(stdexec::get_env(__receiver_)) - , __stop_callback_fn_t{this} - ); - for(auto& __marble : __marbles_) { + stdexec::get_stop_token(stdexec::get_env(__receiver_)), __stop_callback_fn_t{this}); + for (auto& __marble: __marbles_) { __marble.set_origin_frame(__context_->now()); auto& __op = __marble_ops_.emplace_back(); - __op.__emplace_from([this, &__marble](){ - return stdexec::connect( - __schedule_marble(*this, __marble) - , __receiver_t{&__receiver_}); + __op.__emplace_from([this, &__marble]() { + return stdexec::connect(__schedule_marble(*this, __marble), __receiver_t{&__receiver_}); }); } __active_ops_ = __marble_ops_.size(); - for(auto& __op : __marble_ops_) { + for (auto& __op: __marble_ops_) { stdexec::start(__op.value()); } } @@ -881,38 +886,37 @@ namespace exec { std::vector __marbles_; template _Self, class... _Env> - static auto get_item_types(_Self&&, _Env&&...) noexcept - -> item_types { + static auto get_item_types(_Self&&, _Env&&...) noexcept -> item_types { return {}; } template _Self, class... _Env> - static auto get_completion_signatures(_Self&&, _Env&&...) noexcept - -> stdexec::completion_signatures< - stdexec::set_value_t() - , stdexec::set_error_t(std::error_code) - , stdexec::set_error_t(std::exception_ptr) - , stdexec::set_stopped_t()>{ - return {}; - } + static auto + get_completion_signatures(_Self&&, _Env&&...) noexcept -> stdexec::completion_signatures< + stdexec::set_value_t(), + stdexec::set_error_t(std::error_code), + stdexec::set_error_t(std::exception_ptr), + stdexec::set_stopped_t() + > { + return {}; + } static constexpr auto subscribe = - [] _Sequence, stdexec::receiver _Receiver> - (_Sequence&& __sequence, _Receiver __receiver) noexcept - -> __test_sequence_operation> { - return { - static_cast<_Sequence&&>(__sequence).__marbles_ - , static_cast<_Sequence&&>(__sequence).__context_ - , static_cast<_Receiver&&>(__receiver)}; - }; + [] _Sequence, stdexec::receiver _Receiver>( + _Sequence&& __sequence, + _Receiver __receiver) noexcept -> __test_sequence_operation> { + return { + static_cast<_Sequence&&>(__sequence).__marbles_, + static_cast<_Sequence&&>(__sequence).__context_, + static_cast<_Receiver&&>(__receiver)}; + }; }; } // namespace _tst_sched - template + template inline auto test_context::get_marbles_from( - _Sequence&& __sequence - , typename test_clock::duration __stop_after) noexcept - -> std::vector> { + _Sequence&& __sequence, + typename test_clock::duration __stop_after) noexcept -> std::vector> { std::vector> __recording; stdexec::inplace_stop_source __source; @@ -924,18 +928,17 @@ namespace exec { exec::sequence( // schedule connect and start of the sequence being recorded // on the test scheduler - exec::schedule_at(get_scheduler(), __clock.now()) - , record_marbles( - &__recording - , __clock - , static_cast<_Sequence&&>(__sequence)) + exec::schedule_at(get_scheduler(), __clock.now()), + record_marbles(&__recording, __clock, static_cast<_Sequence&&>(__sequence)) // always complete with set_stopped to prevent the following // scheduled request_stop from affecting the clock - , stdexec::just_stopped()) + , + stdexec::just_stopped()) // this is used to stop a 'never' sequence - , exec::schedule_at(get_scheduler(), __clock.now() + __stop_after) - | stdexec::then([&__source]() noexcept { __source.request_stop(); })) - , _tst_sched::__recording_receiver{this, &__source}); + , + exec::schedule_at(get_scheduler(), __clock.now() + __stop_after) + | stdexec::then([&__source]() noexcept { __source.request_stop(); })), + _tst_sched::__recording_receiver{this, &__source}); stdexec::start(__op); // dispatch the test context queues @@ -944,14 +947,15 @@ namespace exec { return __recording; } - inline auto test_context::get_marble_sequence_from(std::vector> __marbles) noexcept + inline auto + test_context::get_marble_sequence_from(std::vector> __marbles) noexcept -> _tst_sched::__test_sequence { - return {this, static_cast>&&>(__marbles)}; - } + return {this, static_cast>&&>(__marbles)}; + } - template + template inline auto test_context::get_marble_sequence_from(stdexec::__mstring<_Len> __diagram) noexcept -> _tst_sched::__test_sequence { - return get_marble_sequence_from(get_marbles_from(__diagram)); - } + return get_marble_sequence_from(get_marbles_from(__diagram)); + } } // namespace exec diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index e2563a4ec..8b8337ceb 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -28,7 +28,7 @@ namespace exec { template struct _WITH_SEQUENCES_; - } + } // namespace __errs template using _WITH_SEQUENCE_ = __errs::_WITH_SEQUENCE_>; @@ -54,19 +54,20 @@ namespace exec { concept __all_contained_in = __v<__mall_contained_in_t<_Needles, _Haystack>>; } // namespace __sequence_sndr - // This concept checks if a given sender satisfies the requirements to be returned from `set_next`. + // This concept checks if a given sender satisfies the requirements to be returned from `set_next`. template > - concept next_sender = stdexec::sender_in<_Sender, _Env> - && __sequence_sndr::__all_contained_in< - stdexec::completion_signatures_of_t<_Sender, _Env>, - stdexec::completion_signatures - >; + concept next_sender = + stdexec::sender_in<_Sender, _Env> + && __sequence_sndr::__all_contained_in< + stdexec::completion_signatures_of_t<_Sender, _Env>, + stdexec::completion_signatures + >; namespace __sequence_sndr { template concept __has_set_next_member = requires(_Receiver& __rcvr, _Item&& __item) { - __rcvr.set_next(static_cast<_Item &&>(__item)); + __rcvr.set_next(static_cast<_Item&&>(__item)); }; // This is a sequence-receiver CPO that is used to apply algorithms on an input sender and it @@ -185,16 +186,17 @@ namespace exec { struct get_item_types_t; template - using __item_types_of_t = - __call_result_t; + using __item_types_of_t = __call_result_t; template - using __unrecognized_sequence_error_t = - __mexception<_UNRECOGNIZED_SEQUENCE_TYPE_<>, _WITH_SEQUENCE_<_Sequence>, _WITH_ENVIRONMENT_<_Env>...>; + using __unrecognized_sequence_error_t = __mexception< + _UNRECOGNIZED_SEQUENCE_TYPE_<>, + _WITH_SEQUENCE_<_Sequence>, + _WITH_ENVIRONMENT_<_Env>... + >; template - using __member_result_t = decltype(__declval<_Sequence>() - .get_item_types(__declval<_Env>())); + using __member_result_t = decltype(__declval<_Sequence>().get_item_types(__declval<_Env>())); template using __static_member_result_t = decltype(STDEXEC_REMOVE_REFERENCE( @@ -205,7 +207,8 @@ namespace exec { transform_sender_result_t<__late_domain_of_t<_Sequence, _Env>, _Sequence, _Env>; template - concept __with_tag_invoke = tag_invocable, _Env>; + concept __with_tag_invoke = + tag_invocable, _Env>; template using __member_alias_t = typename __decay_t<__tfx_sequence_t<_Sequence, _Env>>::item_types; @@ -235,20 +238,22 @@ namespace exec { using __result_t = __static_member_result_t<__tfx_sequence_t, _Env>; return static_cast<__result_t (*)()>(nullptr); } else if constexpr (__with_member<__tfx_sequence_t, _Env>) { - using __result_t = decltype(__declval<__tfx_sequence_t>().get_item_types(__declval<_Env>())); + using __result_t = decltype(__declval<__tfx_sequence_t>() + .get_item_types(__declval<_Env>())); return static_cast<__result_t (*)()>(nullptr); } else if constexpr (__with_tag_invoke<__tfx_sequence_t, _Env>) { using __result_t = tag_invoke_result_t; return static_cast<__result_t (*)()>(nullptr); } else if constexpr ( - sender_in<__tfx_sequence_t, _Env> && !enable_sequence_sender>) { + sender_in<__tfx_sequence_t, _Env> + && !enable_sequence_sender>) { using __result_t = item_types>; return static_cast<__result_t (*)()>(nullptr); } else if constexpr (__is_debug_env<_Env>) { using __tag_invoke::tag_invoke; // This ought to cause a hard error that indicates where the problem is. using __item_types_t - [[maybe_unused]] = tag_invoke_result_t; + [[maybe_unused]] = tag_invoke_result_t; return static_cast<__debug::__item_types (*)()>(nullptr); } else { using __result_t = __unrecognized_sequence_error_t<_Sequence, _Env>; @@ -257,8 +262,8 @@ namespace exec { } template > - constexpr auto - operator()(_Sequence&&, _Env&& = {}) const noexcept -> decltype(__impl<_Sequence, _Env>()()) { + constexpr auto operator()(_Sequence&&, _Env&& = {}) const noexcept + -> decltype(__impl<_Sequence, _Env>()()) { return {}; } }; @@ -273,12 +278,11 @@ namespace exec { template concept has_sequence_item_types = - stdexec::sender_in<_Sequence, _Env...> - && requires(_Sequence&& __sequence, _Env&&... __env) { - { get_item_types(static_cast<_Sequence &&>(__sequence), static_cast<_Env &&>(__env)...) }; + stdexec::sender_in<_Sequence, _Env...> && requires(_Sequence&& __sequence, _Env&&... __env) { + { get_item_types(static_cast<_Sequence&&>(__sequence), static_cast<_Env&&>(__env)...) }; }; - template + template concept sequence_sender = stdexec::sender_in<_Sequence, _Env...> && enable_sequence_sender>; @@ -303,7 +307,7 @@ namespace exec { struct _SEQUENCE_GET_ITEM_TYPES_RESULT_IS_NOT_WELL_FORMED_ { }; template - requires (!stdexec::__merror<_Items>) + requires(!stdexec::__merror<_Items>) auto __check_items(_Items*) -> stdexec::__mexception< _SEQUENCE_GET_ITEM_TYPES_RESULT_IS_NOT_WELL_FORMED_<_Items>, _WITH_SEQUENCE_<_Sequence> @@ -317,71 +321,71 @@ namespace exec { template concept __well_formed_item_types = requires(stdexec::__decay_t<_ItemTypes>* __item_types) { - { exec::__check_items<_Sequence>(__item_types) } -> stdexec::__ok; - }; + { exec::__check_items<_Sequence>(__item_types) } -> stdexec::__ok; + }; template requires stdexec::__merror<_Sequence> auto __check_sequence(_Sequence*) -> _Sequence; - struct _SEQUENCE_GET_ITEM_TYPES_IS_NOT_WELL_FORMED_ {}; + struct _SEQUENCE_GET_ITEM_TYPES_IS_NOT_WELL_FORMED_ { }; template - requires (!stdexec::__merror<_Sequence>) - && (!stdexec::__mvalid<__item_types_of_t, _Sequence>) + requires(!stdexec::__merror<_Sequence>) && (!stdexec::__mvalid<__item_types_of_t, _Sequence>) auto __check_sequence(_Sequence*) -> stdexec::__mexception< _SEQUENCE_GET_ITEM_TYPES_IS_NOT_WELL_FORMED_, _WITH_SEQUENCE_<_Sequence> >; template - requires (!stdexec::__merror<_Sequence>) - && stdexec::__mvalid<__item_types_of_t, _Sequence> - auto __check_sequence(_Sequence*) -> decltype( - exec::__check_items<_Sequence>(static_cast<__item_types_of_t<_Sequence>*>(nullptr))); + requires(!stdexec::__merror<_Sequence>) && stdexec::__mvalid<__item_types_of_t, _Sequence> + auto __check_sequence(_Sequence*) -> decltype(exec::__check_items<_Sequence>( + static_cast<__item_types_of_t<_Sequence>*>(nullptr))); template concept __well_formed_item_senders = requires(stdexec::__decay_t<_Sequence>* __sequence) { - { exec::__check_sequence(__sequence) } -> stdexec::__ok; - }; + { exec::__check_sequence(__sequence) } -> stdexec::__ok; + }; template concept __well_formed_sequence_sender = stdexec::__well_formed_sender<_Sequence> - && enable_sequence_sender> - && __well_formed_item_senders<_Sequence>; + && enable_sequence_sender> + && __well_formed_item_senders<_Sequence>; template - concept sequence_sender_in = sequence_sender<_Sequence, _Env...> - && requires(_Sequence&& __sequence, _Env&&... __env) { - { get_item_types(static_cast<_Sequence &&>(__sequence), static_cast<_Env &&>(__env)...) } - -> __well_formed_item_types<_Sequence>; - }; + concept sequence_sender_in = + sequence_sender<_Sequence, _Env...> && requires(_Sequence&& __sequence, _Env&&... __env) { + { + get_item_types(static_cast<_Sequence&&>(__sequence), static_cast<_Env&&>(__env)...) + } -> __well_formed_item_types<_Sequence>; + }; namespace __debug { - template , class _Sequence> - void __debug_sequence_sender(_Sequence&& __sequence, const _Env& = {}); + template , class _Sequence> + void __debug_sequence_sender(_Sequence&& __sequence, const _Env& = {}); } // namespace __debug using __debug::__debug_sequence_sender; template static constexpr auto __diagnose_sequence_sender_concept_failure(); - #if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() +#if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() // __checked_completion_signatures is for catching logic bugs in a sender's metadata. If sender // and sender_in are both true, then they had better report the same metadata. This // completion signatures wrapper enforces that at compile time. template - auto __checked_item_types(_Sequence && __sequence, _Env &&... __env) noexcept { + auto __checked_item_types(_Sequence&& __sequence, _Env&&... __env) noexcept { using __item_types_t = __item_types_of_t<_Sequence, _Env...>; static_assert(stdexec::__ok<__item_types_t>, "get_item_types returned an error"); - exec::__debug_sequence_sender(static_cast<_Sequence &&>(__sequence), __env...); + exec::__debug_sequence_sender(static_cast<_Sequence&&>(__sequence), __env...); return __item_types_t{}; } template requires sequence_sender<_Sequence, _Env...> - using item_types_of_t = - decltype(exec::__checked_item_types(stdexec::__declval<_Sequence>(), stdexec::__declval<_Env>()...)); + using item_types_of_t = decltype(exec::__checked_item_types( + stdexec::__declval<_Sequence>(), + stdexec::__declval<_Env>()...)); #else template requires sequence_sender_in<_Sequence, _Env...> @@ -473,8 +477,7 @@ namespace exec { stdexec::env_of_t<_Receiver> > >) - || (!sequence_sender_in<_Sequence, stdexec::env_of_t<_Receiver>> - && stdexec::__receiver_from<__sequence_sndr::__stopped_means_break_t<_Receiver>, next_sender_of_t<_Receiver, _Sequence>>) ); + || (!sequence_sender_in<_Sequence, stdexec::env_of_t<_Receiver>> && stdexec::__receiver_from<__sequence_sndr::__stopped_means_break_t<_Receiver>, next_sender_of_t<_Receiver, _Sequence>>) ); namespace __sequence_sndr { struct subscribe_t; @@ -498,17 +501,20 @@ namespace exec { && sender_to, __stopped_means_break_t<_Receiver>>; template - using __subscribe_member_result_t = decltype(__declval<_Sequence>().subscribe(__declval<_Receiver>())); + using __subscribe_member_result_t = decltype(__declval<_Sequence>() + .subscribe(__declval<_Receiver>())); template using __subscribe_static_member_result_t = decltype(STDEXEC_REMOVE_REFERENCE( _Sequence)::subscribe(__declval<_Sequence>(), __declval<_Receiver>())); template - concept __subscribable_with_member = __mvalid<__subscribe_member_result_t, _Sequence, _Receiver>; + concept __subscribable_with_member = + __mvalid<__subscribe_member_result_t, _Sequence, _Receiver>; template - concept __subscribable_with_static_member = __mvalid<__subscribe_static_member_result_t, _Sequence, _Receiver>; + concept __subscribable_with_static_member = + __mvalid<__subscribe_static_member_result_t, _Sequence, _Receiver>; template concept __subscribable_with_tag_invoke = tag_invocable; @@ -521,8 +527,8 @@ namespace exec { // Instantiate __debug_sender via completion_signatures_of_t and // item_types_of_t to check that the actual completions and item_types // match the expected completions and values. - using __checked_signatures - [[maybe_unused]] = __sequence_completion_signatures_of_t<_Sequence, env_of_t<_Receiver>>; + using __checked_signatures [[maybe_unused]] = + __sequence_completion_signatures_of_t<_Sequence, env_of_t<_Receiver>>; using __checked_item_types [[maybe_unused]] = item_types_of_t<_Sequence, env_of_t<_Receiver>>; } else { @@ -541,7 +547,9 @@ namespace exec { __nothrow_callable>; using __tfx_sequence_t = __tfx_sequence_t<_Sequence, _Receiver>; - static_assert(sequence_sender<_Sequence> || has_sequence_item_types<_Sequence, env_of_t<_Receiver>>, "The first argument to stdexec::subscribe must be a sequence sender"); + static_assert( + sequence_sender<_Sequence> || has_sequence_item_types<_Sequence, env_of_t<_Receiver>>, + "The first argument to stdexec::subscribe must be a sequence sender"); static_assert( receiver<_Receiver>, "The second argument to stdexec::subscribe must be a receiver"); #if STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() @@ -563,18 +571,19 @@ namespace exec { >; return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); } else if constexpr (__subscribable_with_static_member<__tfx_sequence_t, _Receiver>) { - using __result_t = decltype(STDEXEC_REMOVE_REFERENCE(__tfx_sequence_t):: - subscribe(__declval<__tfx_sequence_t>(), __declval<_Receiver>())); + using __result_t = decltype(STDEXEC_REMOVE_REFERENCE( + __tfx_sequence_t)::subscribe(__declval<__tfx_sequence_t>(), __declval<_Receiver>())); static_assert( operation_state<__result_t>, "Sequence::subscribe(sender, receiver) must return a type that " "satisfies the operation_state concept"); constexpr bool _Nothrow = _NothrowTfxSequence - && noexcept(STDEXEC_REMOVE_REFERENCE(__tfx_sequence_t) - ::subscribe(__declval<__tfx_sequence_t>(), __declval<_Receiver>())); + && noexcept(STDEXEC_REMOVE_REFERENCE(__tfx_sequence_t)::subscribe( + __declval<__tfx_sequence_t>(), __declval<_Receiver>())); return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); } else if constexpr (__subscribable_with_member<__tfx_sequence_t, _Receiver>) { - using __result_t = decltype(__declval<__tfx_sequence_t>().subscribe(__declval<_Receiver>())); + using __result_t = decltype(__declval<__tfx_sequence_t>() + .subscribe(__declval<_Receiver>())); static_assert( operation_state<__result_t>, "Sequence::subscribe(sender, receiver) must return a type that " @@ -614,17 +623,17 @@ namespace exec { // sender as sequence of one next_sender_of_t<_Receiver, __tfx_sequence_t> __next = set_next( - __rcvr, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); + __rcvr, + stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); return stdexec::connect( static_cast&&>(__next), __stopped_means_break_t<_Receiver>{static_cast<_Receiver&&>(__rcvr)}); // NOLINTNEXTLINE(bugprone-branch-clone) } else if constexpr (__subscribable_with_static_member<__tfx_sequence_t, _Receiver>) { - auto&& __tfx_sequence = transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); - return __tfx_sequence - .subscribe( - static_cast<__tfx_sequence_t&&>(__tfx_sequence), - static_cast<_Receiver&&>(__rcvr)); + auto&& __tfx_sequence = + transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); + return __tfx_sequence.subscribe( + static_cast<__tfx_sequence_t&&>(__tfx_sequence), static_cast<_Receiver&&>(__rcvr)); } else if constexpr (__subscribable_with_member<__tfx_sequence_t, _Receiver>) { return stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env) .subscribe(static_cast<_Receiver&&>(__rcvr)); @@ -638,18 +647,18 @@ namespace exec { // This should generate an instantiate backtrace that contains useful // debugging information. - auto&& __tfx_sequence = stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); - return __tfx_sequence - .subscribe( - static_cast<__tfx_sequence_t&&>(__tfx_sequence), - static_cast<_Receiver&&>(__rcvr)); - } else { + auto&& __tfx_sequence = + stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env); + return __tfx_sequence.subscribe( + static_cast<__tfx_sequence_t&&>(__tfx_sequence), static_cast<_Receiver&&>(__rcvr)); + } else { // sender as sequence of one fallback // This should generate an instantiate backtrace that contains useful // debugging information. next_sender_of_t<_Receiver, __tfx_sequence_t> __next = set_next( - __rcvr, stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); + __rcvr, + stdexec::transform_sender(__domain, static_cast<_Sequence&&>(__sequence), __env)); return stdexec::connect( static_cast&&>(__next), __stopped_means_break_t<_Receiver>{static_cast<_Receiver&&>(__rcvr)}); @@ -677,9 +686,10 @@ namespace exec { template concept sequence_sender_to = - sequence_receiver_from<_Receiver, _Sequence> && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { - subscribe(static_cast<_Sequence &&>(__sequence), static_cast<_Receiver &&>(__rcvr)); - }; + sequence_receiver_from<_Receiver, _Sequence> + && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { + subscribe(static_cast<_Sequence&&>(__sequence), static_cast<_Receiver&&>(__rcvr)); + }; template concept __stoppable_receiver = stdexec::__callable @@ -705,7 +715,7 @@ namespace exec { } //////////////////////////////////////////////////////////////////////////////// -#define STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE \ +#define STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE \ "\n" \ "\n" \ "The given type is not a sequence sender because stdexec::enable_sequence_sender\n" \ @@ -735,43 +745,46 @@ namespace exec { " inline constexpr bool stdexec::enable_sequence_sender = true;\n" //////////////////////////////////////////////////////////////////////////////// -# define STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR \ - "\n" \ - "\n" \ - "Trying to compute the sequences's item types resulted in an error. See\n" \ - "the rest of the compiler diagnostic for clues. Look for the string \"_ERROR_\".\n" +#define STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR \ + "\n" \ + "\n" \ + "Trying to compute the sequences's item types resulted in an error. See\n" \ + "the rest of the compiler diagnostic for clues. Look for the string \"_ERROR_\".\n" //////////////////////////////////////////////////////////////////////////////// -# define STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE \ - "\n" \ - "\n" \ - "The member function `get_item_types` of the sequence returned an\n" \ - "invalid type.\n" \ - "\n" \ - "A sender's `get_item_types` function must return a specialization of\n" \ - "`exec::item_types<...>`, as follows:\n" \ - "\n" \ - " class MySequence\n" \ - " {\n" \ - " public:\n" \ - " using sender_concept = exec::sequence_sender_t;\n" \ - "\n" \ - " template \n" \ - " auto get_item_types(_Env&&...) -> exec::item_types<\n" \ - " // This sequence produces void items...\n" \ - " stdexec::__call_result_t>\n" \ - " {\n" \ - " return {};\n" \ - " }\n" \ - " ...\n" \ - " };\n" +#define STDEXEC_ERROR_GET_ITEM_TYPES_HAS_INVALID_RETURN_TYPE \ + "\n" \ + "\n" \ + "The member function `get_item_types` of the sequence returned an\n" \ + "invalid type.\n" \ + "\n" \ + "A sender's `get_item_types` function must return a specialization of\n" \ + "`exec::item_types<...>`, as follows:\n" \ + "\n" \ + " class MySequence\n" \ + " {\n" \ + " public:\n" \ + " using sender_concept = exec::sequence_sender_t;\n" \ + "\n" \ + " template \n" \ + " auto get_item_types(_Env&&...) -> exec::item_types<\n" \ + " // This sequence produces void items...\n" \ + " stdexec::__call_result_t>\n" \ + " {\n" \ + " return {};\n" \ + " }\n" \ + " ...\n" \ + " };\n" // Used to report a meaningful error message when the sender_in // concept check fails. template static constexpr auto __diagnose_sequence_sender_concept_failure() { - if constexpr (!enable_sequence_sender> && !has_sequence_item_types, _Env...>) { - static_assert(enable_sequence_sender<_Sequence>, STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE); + if constexpr ( + !enable_sequence_sender> + && !has_sequence_item_types, _Env...>) { + static_assert( + enable_sequence_sender<_Sequence>, STDEXEC_ERROR_ENABLE_SEQUENCE_SENDER_IS_FALSE); } else if constexpr (!stdexec::__detail::__consistent_completion_domains<_Sequence>) { static_assert( stdexec::__detail::__consistent_completion_domains<_Sequence>, @@ -780,14 +793,18 @@ namespace exec { "bug in the sequence implementation."); } else if constexpr (!std::move_constructible>) { static_assert( - std::move_constructible>, "The sequence type is not move-constructible."); + std::move_constructible>, + "The sequence type is not move-constructible."); } else if constexpr (!std::constructible_from, _Sequence>) { static_assert( std::constructible_from, _Sequence>, "The sequence cannot be decay-copied. Did you forget a std::move?"); } else { using __items_t = __item_types_of_t<_Sequence, _Env...>; - if constexpr (stdexec::__same_as<__items_t, __sequence_sndr::__unrecognized_sequence_error_t<_Sequence, _Env...>>) { + if constexpr (stdexec::__same_as< + __items_t, + __sequence_sndr::__unrecognized_sequence_error_t<_Sequence, _Env...> + >) { static_assert(stdexec::__mnever<__items_t>, STDEXEC_ERROR_GET_ITEM_TYPES_RETURNED_AN_ERROR); } else if constexpr (stdexec::__merror<__items_t>) { static_assert( @@ -828,7 +845,12 @@ namespace exec { }; template - struct __debug_sequence_sender_receiver<_CvrefSequenceId, _Env, stdexec::completion_signatures<_Sigs...>, item_types<_Items...>> + struct __debug_sequence_sender_receiver< + _CvrefSequenceId, + _Env, + stdexec::completion_signatures<_Sigs...>, + item_types<_Items...> + > : __valid_completions<__normalize_sig_t<_Sigs>...> , __valid_next<_Items...> { using __t = __debug_sequence_sender_receiver; @@ -846,8 +868,15 @@ namespace exec { if constexpr (sequence_sender_in<_Sequence, _Env>) { using __sigs_t = __sequence_completion_signatures_of_t<_Sequence, __debug_env_t<_Env>>; using __item_types_t = __sequence_sndr::__item_types_of_t<_Sequence, __debug_env_t<_Env>>; - using __receiver_t = __debug_sequence_sender_receiver, _Env, __sigs_t, __item_types_t>; - if constexpr (!std::same_as<__sigs_t, __debug::__completion_signatures> || !std::same_as<__item_types_t, __debug::__item_types>) { + using __receiver_t = __debug_sequence_sender_receiver< + stdexec::__cvref_id<_Sequence>, + _Env, + __sigs_t, + __item_types_t + >; + if constexpr ( + !std::same_as<__sigs_t, __debug::__completion_signatures> + || !std::same_as<__item_types_t, __debug::__item_types>) { using __operation_t = exec::subscribe_result_t<_Sequence, __receiver_t>; //static_assert(receiver_of<__receiver_t, __sigs_t>); if constexpr (!std::same_as<__operation_t, __debug_operation>) { diff --git a/test/exec/sequence/test_marbles.cpp b/test/exec/sequence/test_marbles.cpp index 3b18c2f25..4fb59e025 100644 --- a/test/exec/sequence/test_marbles.cpp +++ b/test/exec/sequence/test_marbles.cpp @@ -30,29 +30,28 @@ namespace { - struct __clock_t - { - using duration = std::chrono::milliseconds; - using rep = duration::rep; - using period = duration::period; - using time_point = std::chrono::time_point<__clock_t >; - [[maybe_unused]] static const bool is_steady = true; - - time_point __now_{}; - - [[maybe_unused]] time_point now() noexcept { - return __now_; - } + struct __clock_t { + using duration = std::chrono::milliseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point<__clock_t>; + [[maybe_unused]] + static const bool is_steady = true; + + time_point __now_{}; + + [[maybe_unused]] + time_point now() noexcept { + return __now_; + } }; using __marble_t = exec::marble_t<__clock_t>; using __marbles_t = std::vector<__marble_t>; -# if STDEXEC_HAS_STD_RANGES() +#if STDEXEC_HAS_STD_RANGES() - TEST_CASE( - "marbles - parse empty diagram", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - parse empty diagram", "[sequence_senders][marbles]") { __clock_t __clock{}; auto marbles = get_marbles_from(__clock, ""_mstr); auto expected = __marbles_t{}; @@ -60,9 +59,7 @@ namespace { CHECK(expected == marbles); } - TEST_CASE( - "marbles - parse never diagram", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - parse never diagram", "[sequence_senders][marbles]") { __clock_t __clock{}; auto marbles = get_marbles_from(__clock, "--"_mstr); auto expected = __marbles_t{}; @@ -70,9 +67,7 @@ namespace { CHECK(expected == marbles); } - TEST_CASE( - "marbles - parse never with values diagram", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - parse never with values diagram", "[sequence_senders][marbles]") { __clock_t __clock{}; auto marbles = get_marbles_from(__clock, "-a-b-"_mstr); auto expected = __marbles_t{ @@ -83,9 +78,7 @@ namespace { CHECK(expected == marbles); } - TEST_CASE( - "marbles - parse values diagram", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - parse values diagram", "[sequence_senders][marbles]") { __clock_t __clock{}; auto marbles = get_marbles_from(__clock, "-a-b-|"_mstr); auto expected = __marbles_t{ @@ -97,9 +90,7 @@ namespace { CHECK(expected == marbles); } - TEST_CASE( - "marbles - parse values with skip ms diagram", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - parse values with skip ms diagram", "[sequence_senders][marbles]") { __clock_t __clock{}; auto marbles = get_marbles_from(__clock, "-a- 20ms b-|"_mstr); auto expected = __marbles_t{ @@ -111,9 +102,7 @@ namespace { CHECK(expected == marbles); } - TEST_CASE( - "marbles - parse values with skip s diagram", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - parse values with skip s diagram", "[sequence_senders][marbles]") { __clock_t __clock{}; auto marbles = get_marbles_from(__clock, "-a- 2s b-|"_mstr); auto expected = __marbles_t{ @@ -125,9 +114,7 @@ namespace { CHECK(expected == marbles); } - TEST_CASE( - "marbles - parse values with skip m diagram", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - parse values with skip m diagram", "[sequence_senders][marbles]") { __clock_t __clock{}; auto marbles = get_marbles_from(__clock, "-a- 2m b-|"_mstr); auto expected = __marbles_t{ @@ -139,9 +126,7 @@ namespace { CHECK(expected == marbles); } - TEST_CASE( - "marbles - parse values with skip first diagram", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - parse values with skip first diagram", "[sequence_senders][marbles]") { __clock_t __clock{}; auto marbles = get_marbles_from(__clock, "20ms -a-b-|"_mstr); auto expected = __marbles_t{ @@ -153,31 +138,25 @@ namespace { CHECK(expected == marbles); } - TEST_CASE( - "marbles - record marbles of empty_sequence", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - record marbles of empty_sequence", "[sequence_senders][marbles]") { __clock_t __clock{}; auto actual = record_marbles(__clock, empty_sequence()); auto expected = get_marbles_from(__clock, "=^|"_mstr); CHECK(expected == actual); } - TEST_CASE( - "marbles - record marbles of range", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - record marbles of range", "[sequence_senders][marbles]") { __clock_t __clock{}; auto actual = record_marbles(__clock, range('0', '3')); auto expected = get_marbles_from(__clock, "=^(012|)"_mstr); CHECK(expected == actual); } - TEST_CASE( - "marbles - record marbles of merged ranges", - "[sequence_senders][marbles]") { + TEST_CASE("marbles - record marbles of merged ranges", "[sequence_senders][marbles]") { __clock_t __clock{}; auto actual = record_marbles(__clock, merge(range('0', '2'), range('2', '4'))); auto expected = get_marbles_from(__clock, "=^(0123|)"_mstr); CHECK(expected == actual); } -# endif // STDEXEC_HAS_STD_RANGES() +#endif // STDEXEC_HAS_STD_RANGES() } // namespace diff --git a/test/exec/sequence/test_merge_each.cpp b/test/exec/sequence/test_merge_each.cpp index f15cd1164..062cb10b9 100644 --- a/test/exec/sequence/test_merge_each.cpp +++ b/test/exec/sequence/test_merge_each.cpp @@ -32,27 +32,27 @@ #include #include -# include +#include namespace { template concept __equivalent = __sequence_sndr::__all_contained_in<_A, _B> - && __sequence_sndr::__all_contained_in<_B, _A> - && ex::__v> - == ex::__v>; + && __sequence_sndr::__all_contained_in<_B, _A> + && ex::__v> + == ex::__v>; struct null_receiver { using __id = null_receiver; using __t = null_receiver; using receiver_concept = ex::receiver_t; - template + template void set_value(_Values&&...) noexcept { } template - void set_error(_Error&& ) noexcept { + void set_error(_Error&&) noexcept { } void set_stopped() noexcept { @@ -64,31 +64,36 @@ namespace { } struct ignore_values_fn_t { - template - void operator()(_Vs&&...) const noexcept {} + template + void operator()(_Vs&&...) const noexcept { + } }; template [[nodiscard]] - auto set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) - -> next_sender auto { - return stdexec::upon_error(stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); + auto + set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) -> next_sender auto { + return stdexec::upon_error( + stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); } }; // a sequence adaptor that schedules each item to complete // on the specified scheduler - [[maybe_unused]] static constexpr auto continues_each_on = [](auto sched) { + [[maybe_unused]] + static constexpr auto continues_each_on = [](auto sched) { return exec::transform_each(ex::continues_on(sched)); }; // a sequence adaptor that schedules each item to complete // on the specified scheduler after the specified duration - [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { - return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { - auto at = sched.now() + after; - return sequence(schedule_at(sched, at), stdexec::just(vs...)); - })); - }; + [[maybe_unused]] + static constexpr auto delays_each_on = + [](auto sched, duration_of_t after) noexcept { + return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { + auto at = sched.now() + after; + return sequence(schedule_at(sched, at), stdexec::just(vs...)); + })); + }; #if STDEXEC_HAS_STD_RANGES() @@ -97,36 +102,33 @@ namespace { "[sequence_senders][merge_each][empty_sequence]") { using empty_sequence_t = stdexec::__call_result_t; - [[maybe_unused]] std::array array{ - empty_sequence(), - empty_sequence()}; + [[maybe_unused]] + std::array array{empty_sequence(), empty_sequence()}; - [[maybe_unused]] auto sequences = iterate(std::views::all(array)); + [[maybe_unused]] + auto sequences = iterate(std::views::all(array)); using sequences_t = decltype(sequences); - STATIC_REQUIRE(ex::__ok< - item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - stdexec::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>); - [[maybe_unused]] auto merged = merge_each(sequences); + [[maybe_unused]] + auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok< - item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - stdexec::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>); STATIC_REQUIRE(ex::__callable); int count = 0; - auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ - ++count; - UNSCOPED_INFO("item: " << x - << ", on thread id: " << std::this_thread::get_id()); - }))); + auto v = ex::sync_wait(ignore_all_values( + merged | then_each([&count](int x) { + ++count; + UNSCOPED_INFO("item: " << x << ", on thread id: " << std::this_thread::get_id()); + }))); CHECK(count == 0); CHECK(v.has_value() == true); @@ -138,35 +140,32 @@ namespace { using range_sender_t = stdexec::__call_result_t; - [[maybe_unused]] std::array array{ - range(100,120), - range(200,220)}; + [[maybe_unused]] + std::array array{range(100, 120), range(200, 220)}; - [[maybe_unused]] auto sequences = iterate(std::views::all(array)); + [[maybe_unused]] + auto sequences = iterate(std::views::all(array)); using sequences_t = decltype(sequences); - STATIC_REQUIRE(ex::__ok< - item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - stdexec::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>); - [[maybe_unused]] auto merged = merge_each(sequences); + [[maybe_unused]] + auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok< - item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - stdexec::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>); STATIC_REQUIRE(ex::__callable); int count = 0; - auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ - ++count; - UNSCOPED_INFO("item: " << x - << ", on thread id: " << std::this_thread::get_id()); - }))); + auto v = ex::sync_wait(ignore_all_values( + merged | then_each([&count](int x) { + ++count; + UNSCOPED_INFO("item: " << x << ", on thread id: " << std::this_thread::get_id()); + }))); CHECK(count == 40); CHECK(v.has_value() == true); @@ -178,77 +177,79 @@ namespace { using range_sequence_t = stdexec::__call_result_t; STATIC_REQUIRE(__well_formed_sequence_sender); - STATIC_REQUIRE_FALSE(std::same_as< - item_types_of_t, - item_types<>>); - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_error_t(std::exception_ptr), - ex::set_stopped_t(), - ex::set_value_t()>>); + STATIC_REQUIRE_FALSE(std::same_as, item_types<>>); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_error_t(std::exception_ptr), + ex::set_stopped_t(), + ex::set_value_t() + > + >); using just_range_sender_t = ex::__call_result_t; - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_value_t(range_sequence_t)>>); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures + >); using empty_sequence_t = stdexec::__call_result_t; STATIC_REQUIRE(__well_formed_sequence_sender); - STATIC_REQUIRE(std::same_as< - item_types_of_t, - item_types<>>); - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_value_t()>>); + STATIC_REQUIRE(std::same_as, item_types<>>); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures + >); using just_empty_sender_t = ex::__call_result_t; - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_value_t(empty_sequence_t)>>); - - auto sequences = merge( - ex::just(range(100, 120)), - ex::just(empty_sequence()), - ex::just(range(200, 220))); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures + >); + + auto sequences = + merge(ex::just(range(100, 120)), ex::just(empty_sequence()), ex::just(range(200, 220))); using sequences_t = decltype(sequences); - STATIC_REQUIRE(ex::__ok< - __item_types_of_t>); - STATIC_REQUIRE(ex::__ok< - ex::completion_signatures_of_t>); + STATIC_REQUIRE(ex::__ok<__item_types_of_t>); + STATIC_REQUIRE(ex::__ok>); - STATIC_REQUIRE(__equivalent< - __item_types_of_t, - item_types>); - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_stopped_t(), - ex::set_value_t()>>); + STATIC_REQUIRE( + __equivalent< + __item_types_of_t, + item_types + >); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures + >); auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok< - __item_types_of_t>); - STATIC_REQUIRE(__equivalent< - ex::completion_signatures_of_t, - ex::completion_signatures< - ex::set_error_t(std::exception_ptr), - ex::set_stopped_t(), - ex::set_value_t()>>); + STATIC_REQUIRE(ex::__ok<__item_types_of_t>); + STATIC_REQUIRE( + __equivalent< + ex::completion_signatures_of_t, + ex::completion_signatures< + ex::set_error_t(std::exception_ptr), + ex::set_stopped_t(), + ex::set_value_t() + > + >); int count = 0; - auto v = ex::sync_wait(ignore_all_values(merged | then_each([&count](int x){ - ++count; - UNSCOPED_INFO("item: " << x - << ", on thread id: " << std::this_thread::get_id()); - }))); + auto v = ex::sync_wait(ignore_all_values( + merged | then_each([&count](int x) { + ++count; + UNSCOPED_INFO("item: " << x << ", on thread id: " << std::this_thread::get_id()); + }))); CHECK(count == 40); CHECK(v.has_value() == true); @@ -262,13 +263,11 @@ namespace { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence0 = __test.get_marble_sequence_from( - " 0--2|"_mstr); - auto __sequence1 = __test.get_marble_sequence_from( - " -1-3 -4|"_mstr); - auto expected = get_marbles_from(__clock, - "=^01-(23)-4|"_mstr); - auto actual = __test.get_marbles_from(merge_each(merge(stdexec::just(__sequence0), stdexec::just(__sequence1)))); + auto __sequence0 = __test.get_marble_sequence_from(" 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from(" -1-3 -4|"_mstr); + auto expected = get_marbles_from(__clock, "=^01-(23)-4|"_mstr); + auto actual = __test.get_marbles_from( + merge_each(merge(stdexec::just(__sequence0), stdexec::just(__sequence1)))); CHECK(test_clock::time_point{6ms} == __clock.now()); CAPTURE(__sequence0.__marbles_); CAPTURE(__sequence1.__marbles_); @@ -282,14 +281,12 @@ namespace { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence0 = __test.get_marble_sequence_from( - " 0--2|"_mstr); - auto __sequence1 = __test.get_marble_sequence_from( - " -1-3-4|"_mstr); - auto expected = get_marbles_from(__clock, - "=^0--2-1-3-4|"_mstr); + auto __sequence0 = __test.get_marble_sequence_from(" 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from(" -1-3-4|"_mstr); + auto expected = get_marbles_from(__clock, "=^0--2-1-3-4|"_mstr); std::array<_tst_sched::__test_sequence, 2> __sequences{__sequence0, __sequence1}; - auto actual = __test.get_marbles_from(merge_each(iterate(__test.get_scheduler(), std::views::all(__sequences)))); + auto actual = __test.get_marbles_from( + merge_each(iterate(__test.get_scheduler(), std::views::all(__sequences)))); CHECK(test_clock::time_point{10ms} == __clock.now()); CAPTURE(__sequence0.__marbles_); CAPTURE(__sequence1.__marbles_); @@ -303,14 +300,14 @@ namespace { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence0 = __test.get_marble_sequence_from( - " 0--2|"_mstr); - auto __sequence1 = __test.get_marble_sequence_from( - " -1-3#-4|"_mstr); - auto expected = get_marbles_from(__clock, + auto __sequence0 = __test.get_marble_sequence_from(" 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from(" -1-3#-4|"_mstr); + auto expected = get_marbles_from( + __clock, // TODO FIX set_stopped issued instead of set_error "=^01-(23)#$"_mstr); - auto actual = __test.get_marbles_from(merge_each(merge(stdexec::just(__sequence0), stdexec::just(__sequence1)))); + auto actual = __test.get_marbles_from( + merge_each(merge(stdexec::just(__sequence0), stdexec::just(__sequence1)))); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence0.__marbles_); CAPTURE(__sequence1.__marbles_); @@ -325,15 +322,15 @@ namespace { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence0 = __test.get_marble_sequence_from( - " 0--2|"_mstr); - auto __sequence1 = __test.get_marble_sequence_from( - " -1-3#-4|"_mstr); - auto expected = get_marbles_from(__clock, + auto __sequence0 = __test.get_marble_sequence_from(" 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from(" -1-3#-4|"_mstr); + auto expected = get_marbles_from( + __clock, // TODO FIX set_stopped issued instead of set_error "=^0--2-1-3#$"_mstr); std::array<_tst_sched::__test_sequence, 2> __sequences{__sequence0, __sequence1}; - auto actual = __test.get_marbles_from(merge_each(iterate(__test.get_scheduler(), std::views::all(__sequences)))); + auto actual = __test.get_marbles_from( + merge_each(iterate(__test.get_scheduler(), std::views::all(__sequences)))); CHECK(test_clock::time_point{8ms} == __clock.now()); CAPTURE(__sequence0.__marbles_); CAPTURE(__sequence1.__marbles_); @@ -347,14 +344,14 @@ namespace { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence0 = __test.get_marble_sequence_from( - " 0--2|"_mstr); - auto __sequence1 = __test.get_marble_sequence_from( - " -1-3.-4|"_mstr); - auto expected = get_marbles_from(__clock, + auto __sequence0 = __test.get_marble_sequence_from(" 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from(" -1-3.-4|"_mstr); + auto expected = get_marbles_from( + __clock, // TODO FIX set_stopped issued instead of set_error "=^01-(23).$"_mstr); - auto actual = __test.get_marbles_from(merge_each(merge(stdexec::just(__sequence0), stdexec::just(__sequence1)))); + auto actual = __test.get_marbles_from( + merge_each(merge(stdexec::just(__sequence0), stdexec::just(__sequence1)))); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence0.__marbles_); CAPTURE(__sequence1.__marbles_); @@ -368,15 +365,15 @@ namespace { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence0 = __test.get_marble_sequence_from( - " 0--2|"_mstr); - auto __sequence1 = __test.get_marble_sequence_from( - " -1-3.-4|"_mstr); - auto expected = get_marbles_from(__clock, + auto __sequence0 = __test.get_marble_sequence_from(" 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from(" -1-3.-4|"_mstr); + auto expected = get_marbles_from( + __clock, // TODO FIX set_stopped issued instead of set_error "=^0--2-1-3.$"_mstr); std::array<_tst_sched::__test_sequence, 2> __sequences{__sequence0, __sequence1}; - auto actual = __test.get_marbles_from(merge_each(iterate(__test.get_scheduler(), std::views::all(__sequences)))); + auto actual = __test.get_marbles_from( + merge_each(iterate(__test.get_scheduler(), std::views::all(__sequences)))); CHECK(test_clock::time_point{8ms} == __clock.now()); CAPTURE(__sequence0.__marbles_); CAPTURE(__sequence1.__marbles_); @@ -384,7 +381,7 @@ namespace { } // TODO - fix problem with stopping -#if 0 +# if 0 TEST_CASE( "merge_each - merge_each sender stops when a nested sequence fails", "[sequence_senders][merge_each][merge][iterate]") { @@ -408,8 +405,8 @@ namespace { CHECK(count == 20); CHECK(v.has_value() == false); } - #endif // 0 +# endif // 0 #endif // STDEXEC_HAS_STD_RANGES() -} +} // namespace diff --git a/test/exec/sequence/test_merge_each_threaded.cpp b/test/exec/sequence/test_merge_each_threaded.cpp index 1e8d3a2f5..d215c5d11 100644 --- a/test/exec/sequence/test_merge_each_threaded.cpp +++ b/test/exec/sequence/test_merge_each_threaded.cpp @@ -33,9 +33,9 @@ #include #include -# include -# include -# include +#include +#include +#include namespace { using namespace std::chrono_literals; @@ -44,21 +44,21 @@ namespace { template concept __equivalent = __sequence_sndr::__all_contained_in<_A, _B> - && __sequence_sndr::__all_contained_in<_B, _A> - && ex::__v> - == ex::__v>; + && __sequence_sndr::__all_contained_in<_B, _A> + && ex::__v> + == ex::__v>; struct null_receiver { using __id = null_receiver; using __t = null_receiver; using receiver_concept = ex::receiver_t; - template + template void set_value(_Values&&...) noexcept { } template - void set_error(_Error&& ) noexcept { + void set_error(_Error&&) noexcept { } void set_stopped() noexcept { @@ -70,104 +70,113 @@ namespace { } struct ignore_values_fn_t { - template - void operator()(_Vs&&...) const noexcept {} + template + void operator()(_Vs&&...) const noexcept { + } }; template [[nodiscard]] - auto set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) - -> next_sender auto { - return stdexec::upon_error(stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); + auto + set_next(_Item&& __item) & noexcept(ex::__nothrow_decay_copyable<_Item>) -> next_sender auto { + return stdexec::upon_error( + stdexec::then(static_cast<_Item&&>(__item), ignore_values_fn_t{}), ignore_values_fn_t{}); } }; // a sequence adaptor that applies a function to each item - [[maybe_unused]] static constexpr auto then_each = [](auto f) { + [[maybe_unused]] + static constexpr auto then_each = [](auto f) { return exec::transform_each(ex::then(f)); }; // a sequence adaptor that schedules each item to complete // on the specified scheduler - [[maybe_unused]] static constexpr auto continues_each_on = [](auto sched) { + [[maybe_unused]] + static constexpr auto continues_each_on = [](auto sched) { return exec::transform_each(ex::continues_on(sched)); }; // a sequence adaptor that schedules each item to complete // on the specified scheduler after the specified duration - [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { - return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { - return sequence(schedule_after(sched, after), stdexec::just(vs...)); - })); - }; + [[maybe_unused]] + static constexpr auto delays_each_on = + [](auto sched, duration_of_t after) noexcept { + return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { + return sequence(schedule_after(sched, after), stdexec::just(vs...)); + })); + }; // a sequence adaptor that applies a function to each item // the function must produce a sequence // all the sequences returned from the function are merged - [[maybe_unused]] static constexpr auto flat_map = [](auto&& f) { + [[maybe_unused]] + static constexpr auto flat_map = [](auto&& f) { auto map_merge = [](auto&& sequence, auto&& f) noexcept { - return merge_each(exec::transform_each( - static_cast(sequence), - ex::then(static_cast(f)))); - }; - return stdexec::__binder_back{{static_cast(f)}, {}, {}}; + return merge_each( + exec::transform_each( + static_cast(sequence), ex::then(static_cast(f)))); + }; + return stdexec::__binder_back{ + {static_cast(f)}, {}, {}}; }; // when_all requires a successful completion // however stop_after_on has no successful completion // this uses variant_sender to add a successful completion // (the successful completion will never occur) - [[maybe_unused]] static constexpr auto with_void = [](auto&& sender) noexcept - -> variant_sender< - stdexec::__call_result_t, - decltype(sender)> { + [[maybe_unused]] + static constexpr auto with_void = [](auto&& sender) noexcept + -> variant_sender, decltype(sender)> { return {static_cast(sender)}; }; // with_stop_token_from adds get_stop_token query, that returns the // token for the provided stop_source, to the receiver env - [[maybe_unused]] static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { + [[maybe_unused]] + static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { return ex::write_env(ex::prop{ex::get_stop_token, stop_source.get_token()}); }; // log_start completes with the provided sequence after printing provided string - [[maybe_unused]] auto log_start = [](auto sequence, auto message) { + [[maybe_unused]] + auto log_start = [](auto sequence, auto message) { return exec::sequence( - ex::read_env(ex::get_stop_token) - | stdexec::then([message](auto&& token) noexcept { - UNSCOPED_INFO(message - << (token.stop_requested() ? ", stop was requested" : ", stop not requested") - << ", on thread id: " << std::this_thread::get_id()); - }), - ex::just(sequence)); + ex::read_env(ex::get_stop_token) | stdexec::then([message](auto&& token) noexcept { + UNSCOPED_INFO( + message << (token.stop_requested() ? ", stop was requested" : ", stop not requested") + << ", on thread id: " << std::this_thread::get_id()); + }), + ex::just(sequence)); }; // log_sequence prints the message when each value in the sequence is emitted - [[maybe_unused]] auto log_sequence = [](auto sequence, auto message) { - return sequence - | then_each([message](auto&& value) mutable noexcept { - UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); - return value; - }); + [[maybe_unused]] + auto log_sequence = [](auto sequence, auto message) { + return sequence | then_each([message](auto&& value) mutable noexcept { + UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); + return value; + }); }; // emits_stopped completes with set_stopped after printing info - [[maybe_unused]] auto emits_stopped = []() { - return ex::just() - | stdexec::let_value([]() noexcept { - UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); - return ex::just_stopped(); - }); + [[maybe_unused]] + auto emits_stopped = []() { + return ex::just() | stdexec::let_value([]() noexcept { + UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); }; // emits_error completes with set_error(error) after printing info - [[maybe_unused]] auto emits_error = [](auto error) { - return ex::just() - | stdexec::let_value([error]() noexcept { - UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); + [[maybe_unused]] + auto emits_error = [](auto error) { + return ex::just() | stdexec::let_value([error]() noexcept { + UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); }; #if STDEXEC_HAS_STD_RANGES() // a sequence of numbers from itoa() - [[maybe_unused]] static constexpr auto range = [](auto from, auto to) { + [[maybe_unused]] + static constexpr auto range = [](auto from, auto to) { return exec::iterate(std::views::iota(from, to)); }; - template + template struct as_sequence_t : Sender { using sender_concept = sequence_sender_t; using item_types = exec::item_types; @@ -196,18 +205,19 @@ namespace { int count = 0; - auto v = ex::sync_wait(ignore_all_values(merged | continues_each_on(sched0) | then_each([&count](int x){ - ++count; - UNSCOPED_INFO("item: " << x - << ", on thread id: " << std::this_thread::get_id()); - }))); + auto v = ex::sync_wait(ignore_all_values( + merged | continues_each_on(sched0) | then_each([&count](int x) { + ++count; + UNSCOPED_INFO("item: " << x << ", on thread id: " << std::this_thread::get_id()); + }))); CHECK(count == 40); CHECK(v.has_value() == true); } TEST_CASE( - "merge_each - merge_each sender stops on failed item while merging all items from multiple threads", + "merge_each - merge_each sender stops on failed item while merging all items from multiple " + "threads", "[sequence_senders][static_thread_pool][merge_each][merge][iterate]") { exec::static_thread_pool ctx0{1}; @@ -216,62 +226,62 @@ namespace { ex::scheduler auto sched1 = ctx1.get_scheduler(); auto origin = now(sched1); - auto elapsed_ms = [&sched1, origin](){ + auto elapsed_ms = [&sched1, origin]() { using namespace std::chrono; return duration_cast(now(sched1) - origin).count(); }; auto stop_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after) { - return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms]() noexcept { - UNSCOPED_INFO("requesting stop - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_stopped(); - }); - }; + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms]() noexcept { + UNSCOPED_INFO( + "requesting stop - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); + }; - auto error_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { + auto error_after_on = + [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms, error]() noexcept { - UNSCOPED_INFO(error.what() << " - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms, error]() noexcept { + UNSCOPED_INFO( + error.what() << " - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); }; // a sequence whose items are sequences auto sequences = merge( - ex::just(range(100, 120)), // int items - ex::just(empty_sequence()), // no items - ex::just(range(200, 220)), // int items - ex::just(error_after_on(sched1, 40ms, - std::runtime_error{"failed sequence "})) // no items + ex::just(range(100, 120)), // int items + ex::just(empty_sequence()), // no items + ex::just(range(200, 220)), // int items + ex::just(error_after_on(sched1, 40ms, std::runtime_error{"failed sequence "})) // no items ); // apply delays_each_on to every sequence item and // merge all the new sequences - auto merged = sequences - | flat_map([sched1](auto sequence){ - return sequence | delays_each_on(sched1, 10ms); - }); + auto merged = sequences | flat_map([sched1](auto sequence) { + return sequence | delays_each_on(sched1, 10ms); + }); int count = 0; auto v = ex::sync_wait( ex::when_all( with_void(stop_after_on(sched1, 50ms)), - ignore_all_values(merged - | continues_each_on(sched0) // serializes output on the sched0 strand - | then_each([&count, elapsed_ms](int x){ + ignore_all_values( + merged | continues_each_on(sched0) // serializes output on the sched0 strand + | then_each([&count, elapsed_ms](int x) { ++count; - UNSCOPED_INFO("item: " << x - << ", arrived at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); + UNSCOPED_INFO( + "item: " << x << ", arrived at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); return count; - })) - )); + })))); CHECK(v.has_value() == false); CHECK(count < 40); @@ -288,62 +298,62 @@ namespace { ex::scheduler auto sched1 = ctx1.get_scheduler(); auto origin = now(sched1); - auto elapsed_ms = [&sched1, origin](){ + auto elapsed_ms = [&sched1, origin]() { using namespace std::chrono; return duration_cast(now(sched1) - origin).count(); }; auto stop_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after) { - return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms]() noexcept { - UNSCOPED_INFO("requesting stop - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_stopped(); - }); - }; + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms]() noexcept { + UNSCOPED_INFO( + "requesting stop - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); + }; - auto error_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { + auto error_after_on = + [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms, error]() noexcept { - UNSCOPED_INFO(error.what() << " - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms, error]() noexcept { + UNSCOPED_INFO( + error.what() << " - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); }; // a sequence whose items are sequences auto sequences = merge( - ex::just(range(100, 120)), // int items - ex::just(empty_sequence()), // no items - ex::just(range(200, 220)), // int items - ex::just(error_after_on(sched1, 50ms, - std::runtime_error{"failed sequence "})) // no items + ex::just(range(100, 120)), // int items + ex::just(empty_sequence()), // no items + ex::just(range(200, 220)), // int items + ex::just(error_after_on(sched1, 50ms, std::runtime_error{"failed sequence "})) // no items ); // apply delays_each_on to every sequence item and // merge all the new sequences - auto merged = sequences - | flat_map([sched1](auto sequence){ - return sequence | delays_each_on(sched1, 10ms); - }); + auto merged = sequences | flat_map([sched1](auto sequence) { + return sequence | delays_each_on(sched1, 10ms); + }); int count = 0; auto v = ex::sync_wait( ex::when_all( with_void(stop_after_on(sched1, 40ms)), - ignore_all_values(merged - | continues_each_on(sched0) // serializes output on the sched0 strand - | then_each([&count, elapsed_ms](int x){ + ignore_all_values( + merged | continues_each_on(sched0) // serializes output on the sched0 strand + | then_each([&count, elapsed_ms](int x) { ++count; - UNSCOPED_INFO("item: " << x - << ", arrived at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); + UNSCOPED_INFO( + "item: " << x << ", arrived at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); return count; - })) - )); + })))); CHECK(v.has_value() == false); CHECK(count < 40); @@ -352,4 +362,4 @@ namespace { #endif // STDEXEC_HAS_STD_RANGES() -} +} // namespace diff --git a/test/exec/sequence/test_test_scheduler.cpp b/test/exec/sequence/test_test_scheduler.cpp index 33c175bf6..ca6c50ee2 100644 --- a/test/exec/sequence/test_test_scheduler.cpp +++ b/test/exec/sequence/test_test_scheduler.cpp @@ -35,26 +35,27 @@ namespace { // a sequence adaptor that schedules each item to complete // on the specified scheduler - [[maybe_unused]] static constexpr auto continues_each_on = [](auto sched) { + [[maybe_unused]] + static constexpr auto continues_each_on = [](auto sched) { return exec::transform_each(ex::continues_on(sched)); }; // a sequence adaptor that schedules each item to complete // on the specified scheduler after the specified duration - [[maybe_unused]] static constexpr auto delays_each_on = [](auto sched, duration_of_t after) noexcept { - return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { - auto at = sched.now() + after; - return sequence(schedule_at(sched, at), stdexec::just(vs...)); - })); - }; + [[maybe_unused]] + static constexpr auto delays_each_on = + [](auto sched, duration_of_t after) noexcept { + return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { + auto at = sched.now() + after; + return sequence(schedule_at(sched, at), stdexec::just(vs...)); + })); + }; using __marble_t = exec::marble_t; using __marbles_t = std::vector<__marble_t>; -# if STDEXEC_HAS_STD_RANGES() +#if STDEXEC_HAS_STD_RANGES() - TEST_CASE( - "test_scheduler - parse empty diagram", - "[sequence_senders][test_scheduler][marbles]") { + TEST_CASE("test_scheduler - parse empty diagram", "[sequence_senders][test_scheduler][marbles]") { test_context __test{}; auto __clock = __test.get_clock(); auto marbles = get_marbles_from(__clock, ""_mstr); @@ -64,8 +65,8 @@ namespace { } TEST_CASE( - "test_scheduler - record marbles via test_context", - "[sequence_senders][test_scheduler]") { + "test_scheduler - record marbles via test_context", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(__clock.now() == test_clock::time_point{0ms}); @@ -73,36 +74,33 @@ namespace { auto __sequence = __scheduler.schedule() | stdexec::then([]() noexcept { return '0'; }); auto actual = __test.get_marbles_from(__sequence); CHECK(__clock.now() == test_clock::time_point{0ms}); - auto expected = get_marbles_from(__clock, - "=^(0|)"_mstr); + auto expected = get_marbles_from(__clock, "=^(0|)"_mstr); CHECK(actual == expected); } TEST_CASE( - "test_scheduler - test_context schedule_after advances test_clock", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context schedule_after advances test_clock", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(__clock.now() == test_clock::time_point{0ms}); auto __scheduler = __test.get_scheduler(); - auto __sequence = schedule_after(__scheduler, 2ms) | stdexec::then([]() noexcept { return '0'; }); - auto expected = get_marbles_from(__clock, - "=^--(0|)"_mstr); + auto __sequence = schedule_after(__scheduler, 2ms) + | stdexec::then([]() noexcept { return '0'; }); + auto expected = get_marbles_from(__clock, "=^--(0|)"_mstr); auto actual = __test.get_marbles_from(__sequence); CHECK(test_clock::time_point{2ms} == __clock.now()); CHECK(expected == actual); } TEST_CASE( - "test_scheduler - test_context marble-sequence advances test_clock", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context marble-sequence advances test_clock", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence = __test.get_marble_sequence_from( - " -a--b---c|"_mstr); - auto expected = get_marbles_from(__clock, - "=^-a--b---c|"_mstr); + auto __sequence = __test.get_marble_sequence_from(" -a--b---c|"_mstr); + auto expected = get_marbles_from(__clock, "=^-a--b---c|"_mstr); auto actual = __test.get_marbles_from(__sequence); CHECK(test_clock::time_point{9ms} == __clock.now()); CAPTURE(__sequence.__marbles_); @@ -110,111 +108,97 @@ namespace { } TEST_CASE( - "test_scheduler - test_context marble-sequence never", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context marble-sequence never", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence = __test.get_marble_sequence_from( - " -0-"_mstr); - auto expected = get_marbles_from(__clock, - "=^-5 998ms $"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + auto __sequence = __test.get_marble_sequence_from(" -0-"_mstr); + auto expected = get_marbles_from(__clock, "=^-5 998ms $"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); CHECK(test_clock::time_point{1000ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); } TEST_CASE( - "test_scheduler - test_context marble-sequence error", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context marble-sequence error", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence = __test.get_marble_sequence_from( - " -0--#"_mstr); - auto expected = get_marbles_from(__clock, - "=^-5--#$"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + auto __sequence = __test.get_marble_sequence_from(" -0--#"_mstr); + auto expected = get_marbles_from(__clock, "=^-5--#$"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); } TEST_CASE( - "test_scheduler - test_context marble-sequence error in middle", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context marble-sequence error in middle", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence = __test.get_marble_sequence_from( - " -0--#--1|"_mstr); - auto expected = get_marbles_from(__clock, - "=^-5--#$"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + auto __sequence = __test.get_marble_sequence_from(" -0--#--1|"_mstr); + auto expected = get_marbles_from(__clock, "=^-5--#$"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); } TEST_CASE( - "test_scheduler - test_context marble-sequence stopped", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context marble-sequence stopped", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence = __test.get_marble_sequence_from( - " -0--."_mstr); - auto expected = get_marbles_from(__clock, - "=^-5--.$"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + auto __sequence = __test.get_marble_sequence_from(" -0--."_mstr); + auto expected = get_marbles_from(__clock, "=^-5--.$"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); } TEST_CASE( - "test_scheduler - test_context marble-sequence stopped in middle", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context marble-sequence stopped in middle", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence = __test.get_marble_sequence_from( - " -0--.--1|"_mstr); - auto expected = get_marbles_from(__clock, - "=^-5--.$"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + auto __sequence = __test.get_marble_sequence_from(" -0--.--1|"_mstr); + auto expected = get_marbles_from(__clock, "=^-5--.$"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); } TEST_CASE( - "test_scheduler - test_context marble-sequence transform", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context marble-sequence transform", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence = __test.get_marble_sequence_from( - " -0--1---2|"_mstr); - auto expected = get_marbles_from(__clock, - "=^-5--6---7|"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c){ return c+5; })); + auto __sequence = __test.get_marble_sequence_from(" -0--1---2|"_mstr); + auto expected = get_marbles_from(__clock, "=^-5--6---7|"_mstr); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); CHECK(test_clock::time_point{9ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); } TEST_CASE( - "test_scheduler - test_context marble-sequence simple shift", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context marble-sequence simple shift", + "[sequence_senders][test_scheduler]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence = __test.get_marble_sequence_from( - " 012--|"_mstr); - auto expected = get_marbles_from(__clock, - "=^--012|"_mstr); + auto __sequence = __test.get_marble_sequence_from(" 012--|"_mstr); + auto expected = get_marbles_from(__clock, "=^--012|"_mstr); auto actual = __test.get_marbles_from(__sequence | delays_each_on(__test.get_scheduler(), 2ms)); CHECK(test_clock::time_point{5ms} == __clock.now()); CAPTURE(__sequence.__marbles_); @@ -222,19 +206,18 @@ namespace { } TEST_CASE( - "test_scheduler - test_context multi-second marble-sequence shift", - "[sequence_senders][test_scheduler]") { + "test_scheduler - test_context multi-second marble-sequence shift", + "[sequence_senders][test_scheduler]") { auto __real_time_now = std::chrono::steady_clock::now(); test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence = __test.get_marble_sequence_from( - " 5s 0 5s 1 5s 2 100ms |"_mstr); - auto expected = get_marbles_from(__clock, - "=^ 5s 100ms 0 5s 1 5s 2 |"_mstr); + auto __sequence = __test.get_marble_sequence_from(" 5s 0 5s 1 5s 2 100ms |"_mstr); + auto expected = get_marbles_from(__clock, "=^ 5s 100ms 0 5s 1 5s 2 |"_mstr); - auto actual = __test.get_marbles_from(__sequence | delays_each_on(__test.get_scheduler(), 100ms), 16s); + auto actual = + __test.get_marbles_from(__sequence | delays_each_on(__test.get_scheduler(), 100ms), 16s); CHECK(test_clock::time_point{5s + 5s + 5s + 100ms + 3ms} == __clock.now()); @@ -246,17 +229,14 @@ namespace { } TEST_CASE( - "test_scheduler - test_context marble-sequence merge", - "[sequence_senders][test_scheduler][merge]") { + "test_scheduler - test_context marble-sequence merge", + "[sequence_senders][test_scheduler][merge]") { test_context __test{}; auto __clock = __test.get_clock(); CHECK(test_clock::time_point{0ms} == __clock.now()); - auto __sequence0 = __test.get_marble_sequence_from( - " 0--2|"_mstr); - auto __sequence1 = __test.get_marble_sequence_from( - " -1-3 -4|"_mstr); - auto expected = get_marbles_from(__clock, - "=^01-(23)-4|"_mstr); + auto __sequence0 = __test.get_marble_sequence_from(" 0--2|"_mstr); + auto __sequence1 = __test.get_marble_sequence_from(" -1-3 -4|"_mstr); + auto expected = get_marbles_from(__clock, "=^01-(23)-4|"_mstr); auto actual = __test.get_marbles_from(merge(__sequence0, __sequence1)); CHECK(test_clock::time_point{6ms} == __clock.now()); CAPTURE(__sequence0.__marbles_); @@ -264,5 +244,5 @@ namespace { CHECK(expected == actual); } -# endif // STDEXEC_HAS_STD_RANGES() +#endif // STDEXEC_HAS_STD_RANGES() } // namespace diff --git a/test/test_common/sequences.hpp b/test/test_common/sequences.hpp index 9a7e975bf..57e558c7f 100644 --- a/test/test_common/sequences.hpp +++ b/test/test_common/sequences.hpp @@ -32,7 +32,7 @@ namespace std { inline std::string to_string(const std::exception_ptr __ex) noexcept { try { std::rethrow_exception(__ex); - } catch(const std::exception& __ex) { + } catch (const std::exception& __ex) { return __ex.what(); } } @@ -78,42 +78,48 @@ namespace exec::__marbles { } // namespace exec::__marbles namespace Catch { - template + template struct StringMaker> { static std::string convert(const std::chrono::time_point<_Clock, _Duration>& __at) { - return std::to_string(std::chrono::duration_cast(__at.time_since_epoch()).count()) + "ms"; + return std::to_string( + std::chrono::duration_cast(__at.time_since_epoch()) + .count()) + + "ms"; } }; - template + template struct StringMaker> { static std::string convert(const std::chrono::duration<_Rep, _Period>& __duration) { - return std::to_string(std::chrono::duration_cast(__duration).count()) + "ms"; + return std::to_string( + std::chrono::duration_cast(__duration).count()) + + "ms"; } }; - template + template struct StringMaker> { static std::string convert(const exec::marble_t<_Clock>& __value) { return to_string(__value); } }; - template + template struct StringMaker> { static std::string convert(const exec::notification_t<_CompletionSignatures>& __value) { return to_string(__value); } }; - template _Tag> + template _Tag> struct StringMaker<_Tag> { static std::string convert(const _Tag& __tag) { return to_string(__tag); @@ -127,7 +133,7 @@ namespace { using ex::operator""_mstr; using namespace std::chrono_literals; - template + template struct as_sequence_t : Sender { using sender_concept = sequence_sender_t; template _Self, class... _Env> @@ -140,78 +146,82 @@ namespace { }; // a sequence adaptor that applies a function to each item - [[maybe_unused]] static constexpr auto then_each = [](auto f) { + [[maybe_unused]] + static constexpr auto then_each = [](auto f) { return exec::transform_each(stdexec::then(f)); }; // a sequence adaptor that applies a function to each item // the function must produce a sequence // all the sequences returned from the function are merged - [[maybe_unused]] static constexpr auto flat_map = [](auto&& f) { + [[maybe_unused]] + static constexpr auto flat_map = [](auto&& f) { auto map_merge = [](auto&& sequence, auto&& f) noexcept { - return merge_each(exec::transform_each( - static_cast(sequence), - ex::then(static_cast(f)))); - }; - return stdexec::__binder_back{{static_cast(f)}, {}, {}}; + return merge_each( + exec::transform_each( + static_cast(sequence), ex::then(static_cast(f)))); + }; + return stdexec::__binder_back{ + {static_cast(f)}, {}, {}}; }; // when_all requires a successful completion // however stop_after_on has no successful completion // this uses variant_sender to add a successful completion // (the successful completion will never occur) - [[maybe_unused]] static constexpr auto with_void = [](auto&& sender) noexcept - -> variant_sender< - stdexec::__call_result_t, - decltype(sender)> { + [[maybe_unused]] + static constexpr auto with_void = [](auto&& sender) noexcept + -> variant_sender, decltype(sender)> { return {static_cast(sender)}; }; // with_stop_token_from adds get_stop_token query, that returns the // token for the provided stop_source, to the receiver env - [[maybe_unused]] static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { + [[maybe_unused]] + static constexpr auto with_stop_token_from = [](auto& stop_source) noexcept { return ex::write_env(ex::prop{ex::get_stop_token, stop_source.get_token()}); }; // log_start completes with the provided sequence after printing provided string - [[maybe_unused]] static constexpr auto log_start = [](auto sequence, auto message) { + [[maybe_unused]] + static constexpr auto log_start = [](auto sequence, auto message) { return exec::sequence( - ex::read_env(ex::get_stop_token) - | stdexec::then([message](auto&& token) noexcept { - UNSCOPED_INFO(message - << (token.stop_requested() ? ", stop was requested" : ", stop not requested") - << ", on thread id: " << std::this_thread::get_id()); - }), - ex::just(sequence)); + ex::read_env(ex::get_stop_token) | stdexec::then([message](auto&& token) noexcept { + UNSCOPED_INFO( + message << (token.stop_requested() ? ", stop was requested" : ", stop not requested") + << ", on thread id: " << std::this_thread::get_id()); + }), + ex::just(sequence)); }; // log_sequence prints the message when each value in the sequence is emitted - [[maybe_unused]] static constexpr auto log_sequence = [](auto sequence, auto message) { - return sequence - | then_each([message](auto&& value) mutable noexcept { - UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); - return value; - }); + [[maybe_unused]] + static constexpr auto log_sequence = [](auto sequence, auto message) { + return sequence | then_each([message](auto&& value) mutable noexcept { + UNSCOPED_INFO(message << ", on thread id: " << std::this_thread::get_id()); + return value; + }); }; // emits_stopped completes with set_stopped after printing info - [[maybe_unused]] static constexpr auto emits_stopped = []() { - return ex::just() - | stdexec::let_value([]() noexcept { - UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); - return ex::just_stopped(); - }); + [[maybe_unused]] + static constexpr auto emits_stopped = []() { + return ex::just() | stdexec::let_value([]() noexcept { + UNSCOPED_INFO("emitting stopped, on thread id: " << std::this_thread::get_id()); + return ex::just_stopped(); + }); }; // emits_error completes with set_error(error) after printing info - [[maybe_unused]] static constexpr auto emits_error = [](auto error) { - return ex::just() - | stdexec::let_value([error]() noexcept { - UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); + [[maybe_unused]] + static constexpr auto emits_error = [](auto error) { + return ex::just() | stdexec::let_value([error]() noexcept { + UNSCOPED_INFO(error.what() << ", on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); }; -# if STDEXEC_HAS_STD_RANGES() +#if STDEXEC_HAS_STD_RANGES() // a sequence of numbers from itoa() - [[maybe_unused]] static constexpr auto range = [](auto from, auto to) { + [[maybe_unused]] + static constexpr auto range = [](auto from, auto to) { return exec::iterate(std::views::iota(from, to)); }; -# endif // STDEXEC_HAS_STD_RANGES() +#endif // STDEXEC_HAS_STD_RANGES() } // namespace From 09c57f30cbdaa626a9ea625b50038aed8a546a24 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 05:33:56 -0700 Subject: [PATCH 29/39] update transform_iterate to work with specified scheduler support in seqeunce iterate --- include/exec/static_thread_pool.hpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/include/exec/static_thread_pool.hpp b/include/exec/static_thread_pool.hpp index b5a3872aa..667a0e7a4 100644 --- a/include/exec/static_thread_pool.hpp +++ b/include/exec/static_thread_pool.hpp @@ -278,9 +278,16 @@ namespace exec { #if STDEXEC_HAS_STD_RANGES() struct transform_iterate { - template - auto operator()(exec::iterate_t, Range&& range) -> __t> { - return {static_cast(range), pool_}; + template + using __range_of_t = + stdexec::__mapply, STDEXEC_REMOVE_REFERENCE(_Data)>; + template + auto operator()(exec::iterate_t, _Data&& data) + -> __t>> { + return { + static_cast<__range_of_t<_Data>&&>( + STDEXEC_REMOVE_REFERENCE(_Data)::template __get<1>(data)), + pool_}; } static_thread_pool_& pool_; From 6bed02b692db7ff2fdfb7cae62d73b6df851d1ae Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 09:30:43 -0700 Subject: [PATCH 30/39] silence visual studio unreachable warnings --- include/exec/sequence/merge_each.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/exec/sequence/merge_each.hpp b/include/exec/sequence/merge_each.hpp index 52a3d8ee3..56229c7f7 100644 --- a/include/exec/sequence/merge_each.hpp +++ b/include/exec/sequence/merge_each.hpp @@ -102,15 +102,17 @@ namespace exec { bool stop_requested() const noexcept { if constexpr (!unstoppable_token<__stop_token_t>) { return __stop_source_.stop_requested(); + } else { + return false; } - return false; } bool request_stop() noexcept { if constexpr (!unstoppable_token<__stop_token_t>) { return __stop_source_.request_stop(); + } else { + return false; } - return false; } inplace_stop_token get_token() const & noexcept { From 9fd303d6e3d63ca69fb27b647f7485e89476fbac Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 09:37:10 -0700 Subject: [PATCH 31/39] fix visual studio errors --- include/exec/sequence/marbles.hpp | 38 ++++++++++++++++++------ include/exec/sequence/notification.hpp | 41 +++++++++++++++----------- test/test_common/sequences.hpp | 21 ------------- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/include/exec/sequence/marbles.hpp b/include/exec/sequence/marbles.hpp index d5e437541..338c00cfb 100644 --- a/include/exec/sequence/marbles.hpp +++ b/include/exec/sequence/marbles.hpp @@ -100,9 +100,9 @@ namespace exec { return __lhs.__c_ == __rhs.__c_; } - friend std::string to_string(const __value_t& __self) noexcept { - using std::to_string; - return "'" + std::string{1, __self.__c_} + "'"; + friend inline std::string to_string(const __value_t& __self) noexcept { + const char __result[4] = {'\'', __self.__c_, '\'', '\0'}; + return __result; } }; @@ -122,6 +122,9 @@ namespace exec { operator marble_selector_t() const noexcept { return marble_selector_t::sequence_start; } + friend inline std::string to_string(sequence_start_t) noexcept { + return {"sequence_start"}; + } }; static constexpr inline sequence_start_t sequence_start; @@ -129,6 +132,9 @@ namespace exec { operator marble_selector_t() const noexcept { return marble_selector_t::sequence_connect; } + friend inline std::string to_string(sequence_connect_t) noexcept { + return {"sequence_connect"}; + } }; static constexpr inline sequence_connect_t sequence_connect; @@ -136,6 +142,9 @@ namespace exec { operator marble_selector_t() const noexcept { return marble_selector_t::sequence_value; } + friend inline std::string to_string(sequence_end_t) noexcept { + return {"sequence_end"}; + } }; static constexpr inline sequence_end_t sequence_end; @@ -143,6 +152,9 @@ namespace exec { operator marble_selector_t() const noexcept { return marble_selector_t::sequence_error; } + friend inline std::string to_string(sequence_error_t) noexcept { + return {"sequence_error"}; + } }; static constexpr inline sequence_error_t sequence_error; @@ -150,6 +162,9 @@ namespace exec { operator marble_selector_t() const noexcept { return marble_selector_t::sequence_stopped; } + friend inline std::string to_string(sequence_stopped_t) noexcept { + return {"sequence_stopped"}; + } }; static constexpr inline sequence_stopped_t sequence_stopped; @@ -157,6 +172,9 @@ namespace exec { operator marble_selector_t() const noexcept { return marble_selector_t::request_stop; } + friend inline std::string to_string(request_stop_t) noexcept { + return {"request_stop"}; + } }; static constexpr inline request_stop_t request_stop; @@ -490,8 +508,11 @@ namespace exec { break; } default: { - long __consumed_in_default = 0; - if (__whole.begin() == __remaining.begin() || !!std::isspace(__remaining.front())) { + // use auto and math to derive the difference type + auto __consumed_in_default = __remaining.begin() - __remaining.begin(); + if ( + std::addressof(*__whole.begin()) == std::addressof(*__remaining.begin()) + || !!std::isspace(__remaining.front())) { if (!!std::isspace(__remaining.front())) { __consume_first(1); ++__consumed_in_default; @@ -508,7 +529,7 @@ namespace exec { if ( __suffix_begin != __remaining.end() && __suffix_begin - __remaining.begin() > 0 && __all_digits) { - long __to_consume = __suffix_begin - __remaining.begin(); + auto __to_consume = __suffix_begin - __remaining.begin(); long __duration = std::atol(__remaining.data()); if (std::ranges::equal( __remaining.subspan(__to_consume, 3), __make_span("ms "_mstr))) { @@ -800,6 +821,8 @@ namespace exec { } // namespace __marbles + using __value_t = __marbles::__value_t; + using sequence_start_t = __marbles::sequence_start_t; static constexpr inline auto sequence_start = sequence_start_t{}; @@ -829,9 +852,6 @@ namespace exec { static constexpr inline auto record_marbles = record_marbles_t{}; - namespace __marbles { - - } } // namespace exec namespace stdexec { diff --git a/include/exec/sequence/notification.hpp b/include/exec/sequence/notification.hpp index f2bf81cda..dd16ee910 100644 --- a/include/exec/sequence/notification.hpp +++ b/include/exec/sequence/notification.hpp @@ -22,6 +22,7 @@ #include "stdexec/__detail/__config.hpp" #include "stdexec/__detail/__tuple.hpp" #include +#include namespace exec { namespace __notification { @@ -116,13 +117,13 @@ namespace exec { template void visit_receiver(_Receiver&& __receiver) noexcept { std::visit( - [&__receiver](auto&& __tuple) noexcept { + [&__receiver](_Tuple&& __tuple) noexcept { __tuple.apply( - [&__receiver](auto __tag, auto&&... __args) noexcept { + [&__receiver](_Tag __tag, _Args&&... __args) noexcept { __tag( - static_cast<_Receiver&&>(__receiver), static_cast(__args)...); + static_cast<_Receiver&&>(__receiver), static_cast<_Args&&>(__args)...); }, - static_cast(__tuple)); + static_cast<_Tuple&&>(__tuple)); }, static_cast<__notification_t&&>(__notification_)); } @@ -156,25 +157,31 @@ namespace exec { friend auto operator==(const notification_t& __lhs, const notification_t& __rhs) noexcept -> bool { return std::visit( - [](const _Lhs& __lhs, const _Rhs& __rhs) noexcept { + [](const _Lhs& __lhs, const _Rhs& __rhs) noexcept -> bool { + using __lhs_tag_t = notification_t::__tag_of_t<_Lhs>; + using __rhs_tag_t = notification_t::__tag_of_t<_Rhs>; if constexpr ( - !std::same_as<__tag_of_t<_Lhs>, __tag_of_t<_Rhs>> + !std::same_as<__lhs_tag_t, __rhs_tag_t> || stdexec::__v> != stdexec::__v>) { return false; } else { return __lhs.apply( - [&__rhs](_LTag, const _LArgs&... __l_args) { - return __rhs.apply( - [&](_RTag, const _RArgs&... __r_args) { - if constexpr ((std::equality_comparable_with - && ... && true)) { - return ((__l_args == __r_args) && ... && true); - } else { - return false; - } - }, - __rhs); + [&__lhs, + &__rhs](_LTag, const _LArgs&...) noexcept -> bool { + return [&__lhs, &__rhs](__indices<_Is...>) { + if constexpr ((std::equality_comparable_with< + const decltype(_Lhs::template __get<_Is+1>(__lhs))&, + const decltype(_Rhs::template __get<_Is+1>(__rhs))& + > + && ... && true)) { + return ( + ((_Lhs::template __get<_Is+1>(__lhs)) == (_Rhs::template __get<_Is+1>(__rhs))) + && ... && true); + } else { + return false; + } + }(__indices_for<_LArgs...>{}); }, __lhs); } diff --git a/test/test_common/sequences.hpp b/test/test_common/sequences.hpp index 57e558c7f..7ae9f5653 100644 --- a/test/test_common/sequences.hpp +++ b/test/test_common/sequences.hpp @@ -56,27 +56,6 @@ namespace exec::__sequence_sender { } } // namespace exec::__sequence_sender -namespace exec::__marbles { - inline std::string to_string(sequence_start_t) noexcept { - return {"sequence_start"}; - } - inline std::string to_string(sequence_connect_t) noexcept { - return {"sequence_connect"}; - } - inline std::string to_string(sequence_end_t) noexcept { - return {"sequence_end"}; - } - inline std::string to_string(sequence_error_t) noexcept { - return {"sequence_error"}; - } - inline std::string to_string(sequence_stopped_t) noexcept { - return {"sequence_stopped"}; - } - inline std::string to_string(request_stop_t) noexcept { - return {"request_stop"}; - } -} // namespace exec::__marbles - namespace Catch { template struct StringMaker> { From a309693d974d218480bf3b7de0cc0a18e8706f7c Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 09:38:03 -0700 Subject: [PATCH 32/39] fixes visual studio compile errors and runtime crashes --- include/exec/sequence/test_scheduler.hpp | 228 +++++++++++++---------- 1 file changed, 134 insertions(+), 94 deletions(-) diff --git a/include/exec/sequence/test_scheduler.hpp b/include/exec/sequence/test_scheduler.hpp index cb18d9079..e6864546d 100644 --- a/include/exec/sequence/test_scheduler.hpp +++ b/include/exec/sequence/test_scheduler.hpp @@ -22,7 +22,6 @@ #include "../__detail/intrusive_heap.hpp" #include "../../stdexec/__detail/__intrusive_mpsc_queue.hpp" -#include "exec/finally.hpp" #include "exec/sequence.hpp" #include "exec/sequence_senders.hpp" #include "exec/variant_sender.hpp" @@ -729,22 +728,23 @@ namespace exec { }; template - struct __test_sequence_operation : __test_sequence_operation_base { + struct __test_sequence_operation_part : __test_sequence_operation_base { using _Receiver = stdexec::__t<_ReceiverId>; using __marble_t = marble_t; using __marble_sender_t = __marble_t::__marble_sender_t; using __time_point_t = typename test_scheduler::time_point; + using __receiver_t = __next_receiver<_ReceiverId>; std::vector<__marble_t> __marbles_; _Receiver __receiver_; __marble_t* __end_marble_{nullptr}; - int __active_ops_ = 0; + std::size_t __active_ops_ = 0; __marble_t __requested_stop_marble_{__time_point_t{}, sequence_stopped}; struct __stop_callback_fn_t { - __test_sequence_operation* __self_; + __test_sequence_operation_part* __self_; void operator()() const noexcept { auto& __self = *__self_; __self.__requested_stop_marble_.set_origin_frame(__self.__context_->now()); @@ -766,83 +766,37 @@ namespace exec { std::optional<__stop_callback_t> __on_stop_; + __test_sequence_operation_part( + std::vector<__marble_t> __marbles, + test_context* __context, + _Receiver&& __receiver) noexcept + : __test_sequence_operation_base{__context} + , __marbles_{static_cast&&>(__marbles)} + , __receiver_{static_cast<_Receiver&&>(__receiver)} { + } + template static auto __schedule_at( - __test_sequence_operation& __self, + __test_sequence_operation_part& __self, __marble_t& __marble, - _Completion&& __completion) noexcept { - return stdexec::write_env( - // schedule the marble completion at the specified frame - exec::sequence( - exec::schedule_at(__self.__context_->get_scheduler(), __marble.frame()), - static_cast<_Completion&&>(__completion)), - stdexec::prop{stdexec::get_stop_token, __self.__stop_source_.get_token()}) - | exec::finally(stdexec::just() | stdexec::then([&__self, &__marble]() noexcept { - // after each completion, update the __test_sequence_operation state - STDEXEC_ASSERT(__self.__active_ops_ > 0); - if ( - __marble.error_notification() || __marble.stopped_notification() - || __marble.sequence_error() || __marble.sequence_stopped() - || __marble.sequence_end()) { - // these marbles trigger the whole sequence - // to complete with no more items - if (!__self.__end_marble_) { - // set as the end marble - // this determines the signal that will be used to - // complete the sequence after all remaining active - // operations have completed - __self.__end_marble_ = &__marble; - } - // cancel all pending ops - __self.request_stop(); - } - if (--__self.__active_ops_ == 0) { - // all ops are complete, - if (!!__self.__end_marble_) { - __self.__on_stop_.reset(); - __self.__end_marble_->visit_sequence_receiver( - static_cast<_Receiver&&>(__self.__receiver_)); - } - // else this sequence never completes - - // this sequence must be stopped externally - } - })); - } - using __receiver_t = __next_receiver<_ReceiverId>; + _Completion&& __completion) noexcept; static auto - __schedule_marble(__test_sequence_operation& __self, __marble_t& __marble) noexcept { - using __next_sender_t = decltype(__schedule_at( - __self, __marble, exec::set_next(__self.__receiver_, __marble.visit_sender()))); - using __end_sender_t = decltype(__schedule_at(__self, __marble, stdexec::just())); - struct __next_sender_id { - using __t = __next_sender_t; - }; - struct __end_sender_id { - using __t = __end_sender_t; - }; - - // WORKAROUND clang 19 would fail to compile the construction of the variant_sender. - // It was unable to find the matching value in the variant that would be constructed. - // __proxy_sender is a hammer to force the types to look different enough to - // distinguish which variant value to construct - using __next_sender_proxy_t = __proxy_sender<__next_sender_id>; - using __end_sender_proxy_t = __proxy_sender<__end_sender_id>; - - using __result_t = variant_sender<__next_sender_proxy_t, __end_sender_proxy_t>; - if (__marble.__notification_.has_value()) { - return __result_t{__next_sender_proxy_t{{__schedule_at( - __self, __marble, exec::set_next(__self.__receiver_, __marble.visit_sender()))}}}; - } else { - return __result_t{ - __end_sender_proxy_t{{__schedule_at(__self, __marble, stdexec::just())}}}; - } - } + __schedule_marble(__test_sequence_operation_part& __self, __marble_t& __marble) noexcept; + }; - using __scheduled_marble_t = stdexec::__call_result_t< - decltype(&__schedule_marble), - __test_sequence_operation&, - __marble_t& - >; + template + struct __test_sequence_operation : __test_sequence_operation_part<_ReceiverId> { + using __part_t = __test_sequence_operation_part<_ReceiverId>; + + using _Receiver = stdexec::__t<_ReceiverId>; + + using __marble_t = typename __part_t::__marble_t; + using __marble_sender_t = typename __part_t::__marble_sender_t; + using __time_point_t = typename __part_t::__time_point_t; + using __receiver_t = typename __part_t::__receiver_t; + + using __scheduled_marble_t = + stdexec::__call_result_t; using __marble_op_t = stdexec::connect_result_t<__scheduled_marble_t, __receiver_t>; std::deque> __marble_ops_{}; @@ -851,28 +805,114 @@ namespace exec { std::vector<__marble_t> __marbles, test_context* __context, _Receiver&& __receiver) noexcept - : __test_sequence_operation_base{__context} - , __marbles_{static_cast&&>(__marbles)} - , __receiver_{static_cast<_Receiver&&>(__receiver)} { + : __part_t{ + static_cast&&>(__marbles), + __context, + static_cast<_Receiver&&>(__receiver)} { } - void start() noexcept { - __on_stop_.emplace( - stdexec::get_stop_token(stdexec::get_env(__receiver_)), __stop_callback_fn_t{this}); - for (auto& __marble: __marbles_) { - __marble.set_origin_frame(__context_->now()); - auto& __op = __marble_ops_.emplace_back(); - __op.__emplace_from([this, &__marble]() { - return stdexec::connect(__schedule_marble(*this, __marble), __receiver_t{&__receiver_}); - }); - } + void start() noexcept; + }; - __active_ops_ = __marble_ops_.size(); - for (auto& __op: __marble_ops_) { - stdexec::start(__op.value()); - } + template + template + auto __test_sequence_operation_part<_ReceiverId>::__schedule_at( + __test_sequence_operation_part<_ReceiverId>& __self, + marble_t& __marble, + _Completion&& __completion) noexcept { + return stdexec::write_env( + // schedule the marble completion at the specified frame + exec::sequence( + exec::schedule_at(__self.__context_->get_scheduler(), __marble.frame()), + static_cast<_Completion&&>(__completion)), + stdexec::prop{stdexec::get_stop_token, __self.__stop_source_.get_token()}) + | stdexec::upon_error([](auto&&) noexcept {}) | stdexec::upon_stopped([]() noexcept {}) + | stdexec::then([&__self, &__marble]() noexcept { + // after each completion, update the __test_sequence_operation_part state + STDEXEC_ASSERT(__self.__active_ops_ > 0); + if ( + __marble.error_notification() || __marble.stopped_notification() + || __marble.sequence_error() || __marble.sequence_stopped() + || __marble.sequence_end()) { + // these marbles trigger the whole sequence + // to complete with no more items + if (!__self.__end_marble_) { + // set as the end marble + // this determines the signal that will be used to + // complete the sequence after all remaining active + // operations have completed + __self.__end_marble_ = &__marble; + } + // cancel all pending ops + __self.request_stop(); + } + if (--__self.__active_ops_ == 0) { + // all ops are complete, + if (!!__self.__end_marble_) { + __self.__on_stop_.reset(); + __self.__end_marble_ + ->visit_sequence_receiver(static_cast<_Receiver&&>(__self.__receiver_)); + } + // else this sequence never completes - + // this sequence must be stopped externally + } + }); + } + + template + auto __test_sequence_operation_part<_ReceiverId>::__schedule_marble( + __test_sequence_operation_part<_ReceiverId>& __self, + marble_t& __marble) noexcept { + + using __next_t = decltype(exec::set_next(__self.__receiver_, __marble.visit_sender())); + using __next_sender_t = + decltype(__schedule_at(__self, __marble, stdexec::__declval<__next_t>())); + using __end_sender_t = decltype(__schedule_at(__self, __marble, stdexec::just())); + struct __next_sender_id { + using __t = __next_sender_t; + }; + struct __end_sender_id { + using __t = __end_sender_t; + }; + + // WORKAROUND clang 19 would fail to compile the construction of the variant_sender. + // It was unable to find the matching value in the variant that would be constructed. + // __proxy_sender is a hammer to force the types to look different enough to + // distinguish which variant value to construct + using __next_sender_proxy_t = __proxy_sender<__next_sender_id>; + using __end_sender_proxy_t = __proxy_sender<__end_sender_id>; + + using __result_t = variant_sender<__next_sender_proxy_t, __end_sender_proxy_t>; + if (__marble.__notification_.has_value()) { + + auto __next = exec::set_next(__self.__receiver_, __marble.visit_sender()); + __next_sender_proxy_t __scheduled( + __schedule_at(__self, __marble, static_cast<__next_t&&>(__next))); + return __result_t{__scheduled}; + } else { + return __result_t{__end_sender_proxy_t{{__schedule_at(__self, __marble, stdexec::just())}}}; } - }; + } + + template + void __test_sequence_operation<_ReceiverId>::start() noexcept { + this->__on_stop_.emplace( + stdexec::get_stop_token(stdexec::get_env(this->__receiver_)), + __part_t::__stop_callback_fn_t(this)); + for (auto& __marble: this->__marbles_) { + __marble.set_origin_frame(this->__context_->now()); + auto& __op = __marble_ops_.emplace_back(); + __op.__emplace_from([this, &__marble]() { + return stdexec::connect( + __part_t::__schedule_marble(*this, __marble), __receiver_t{&this->__receiver_}); + }); + } + + this->__active_ops_ = this->__marble_ops_.size(); + for (auto& __op: this->__marble_ops_) { + stdexec::start(__op.value()); + } + } struct __test_sequence { using __t = __test_sequence; From 3d93782547d30e93d965b0cf13f871e042144f21 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 09:38:32 -0700 Subject: [PATCH 33/39] fix delays_each_on --- test/exec/sequence/test_test_scheduler.cpp | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/test/exec/sequence/test_test_scheduler.cpp b/test/exec/sequence/test_test_scheduler.cpp index ca6c50ee2..3f28d0b66 100644 --- a/test/exec/sequence/test_test_scheduler.cpp +++ b/test/exec/sequence/test_test_scheduler.cpp @@ -43,11 +43,17 @@ namespace { // on the specified scheduler after the specified duration [[maybe_unused]] static constexpr auto delays_each_on = - [](auto sched, duration_of_t after) noexcept { - return exec::transform_each(stdexec::let_value([sched, after](auto&&... vs) noexcept { - auto at = sched.now() + after; - return sequence(schedule_at(sched, at), stdexec::just(vs...)); - })); + [](Sched sched, duration_of_t after) noexcept { + auto delay_value = [](Value&& value, Sched sched, duration_of_t after) { + return sequence(schedule_after(sched, after), static_cast(value)); + }; + auto delay_adaptor = + stdexec::__binder_back>{ + {sched, after}, + {}, + {} + }; + return exec::transform_each(delay_adaptor); }; using __marble_t = exec::marble_t; @@ -115,7 +121,7 @@ namespace { CHECK(test_clock::time_point{0ms} == __clock.now()); auto __sequence = __test.get_marble_sequence_from(" -0-"_mstr); auto expected = get_marbles_from(__clock, "=^-5 998ms $"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) noexcept -> char { return c + 5; })); CHECK(test_clock::time_point{1000ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); @@ -129,7 +135,7 @@ namespace { CHECK(test_clock::time_point{0ms} == __clock.now()); auto __sequence = __test.get_marble_sequence_from(" -0--#"_mstr); auto expected = get_marbles_from(__clock, "=^-5--#$"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) noexcept -> char { return c + 5; })); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); @@ -143,7 +149,7 @@ namespace { CHECK(test_clock::time_point{0ms} == __clock.now()); auto __sequence = __test.get_marble_sequence_from(" -0--#--1|"_mstr); auto expected = get_marbles_from(__clock, "=^-5--#$"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) noexcept -> char { return c + 5; })); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); @@ -157,7 +163,7 @@ namespace { CHECK(test_clock::time_point{0ms} == __clock.now()); auto __sequence = __test.get_marble_sequence_from(" -0--."_mstr); auto expected = get_marbles_from(__clock, "=^-5--.$"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) noexcept -> char { return c + 5; })); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); @@ -171,7 +177,7 @@ namespace { CHECK(test_clock::time_point{0ms} == __clock.now()); auto __sequence = __test.get_marble_sequence_from(" -0--.--1|"_mstr); auto expected = get_marbles_from(__clock, "=^-5--.$"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) noexcept -> char { return c + 5; })); CHECK(test_clock::time_point{4ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); @@ -185,7 +191,7 @@ namespace { CHECK(test_clock::time_point{0ms} == __clock.now()); auto __sequence = __test.get_marble_sequence_from(" -0--1---2|"_mstr); auto expected = get_marbles_from(__clock, "=^-5--6---7|"_mstr); - auto actual = __test.get_marbles_from(__sequence | then_each([](char c) { return c + 5; })); + auto actual = __test.get_marbles_from(__sequence | then_each([](char c) noexcept -> char { return c + 5; })); CHECK(test_clock::time_point{9ms} == __clock.now()); CAPTURE(__sequence.__marbles_); CHECK(expected == actual); From 175e73416adb4785bf8df2d8b4c9da67fe91d6fc Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 10:34:26 -0700 Subject: [PATCH 34/39] fix clang19 build --- include/exec/sequence/notification.hpp | 34 ++++++++++++++++++++++++ include/exec/sequence/test_scheduler.hpp | 2 +- test/test_common/sequences.hpp | 31 --------------------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/include/exec/sequence/notification.hpp b/include/exec/sequence/notification.hpp index dd16ee910..f95f4e7c2 100644 --- a/include/exec/sequence/notification.hpp +++ b/include/exec/sequence/notification.hpp @@ -18,11 +18,45 @@ #include "../../stdexec/concepts.hpp" #include "../../stdexec/execution.hpp" +#include "exec/sequence_senders.hpp" #include "stdexec/__detail/__concepts.hpp" #include "stdexec/__detail/__config.hpp" #include "stdexec/__detail/__tuple.hpp" #include #include +#include +#include + +namespace std { + inline std::string to_string(const std::error_code __error) noexcept { + return __error.message(); + } + inline std::string to_string(const std::exception_ptr __ex) noexcept { + try { + std::rethrow_exception(__ex); + } catch (const std::exception& __ex) { + return __ex.what(); + } + } +} // namespace std + +namespace stdexec::__rcvrs { + inline std::string to_string(set_value_t) noexcept { + return {"set_value"}; + } + inline std::string to_string(set_error_t) noexcept { + return {"set_error"}; + } + inline std::string to_string(set_stopped_t) noexcept { + return {"set_stopped"}; + } +} // namespace stdexec::__rcvrs + +namespace exec::__sequence_sender { + inline std::string to_string(set_next_t) noexcept { + return {"set_next"}; + } +} // namespace exec::__sequence_sender namespace exec { namespace __notification { diff --git a/include/exec/sequence/test_scheduler.hpp b/include/exec/sequence/test_scheduler.hpp index e6864546d..c5a69fd2a 100644 --- a/include/exec/sequence/test_scheduler.hpp +++ b/include/exec/sequence/test_scheduler.hpp @@ -898,7 +898,7 @@ namespace exec { void __test_sequence_operation<_ReceiverId>::start() noexcept { this->__on_stop_.emplace( stdexec::get_stop_token(stdexec::get_env(this->__receiver_)), - __part_t::__stop_callback_fn_t(this)); + typename __part_t::__stop_callback_fn_t(this)); for (auto& __marble: this->__marbles_) { __marble.set_origin_frame(this->__context_->now()); auto& __op = __marble_ops_.emplace_back(); diff --git a/test/test_common/sequences.hpp b/test/test_common/sequences.hpp index 7ae9f5653..6099c3649 100644 --- a/test/test_common/sequences.hpp +++ b/test/test_common/sequences.hpp @@ -25,37 +25,6 @@ #include -namespace std { - inline std::string to_string(const std::error_code __error) noexcept { - return __error.message(); - } - inline std::string to_string(const std::exception_ptr __ex) noexcept { - try { - std::rethrow_exception(__ex); - } catch (const std::exception& __ex) { - return __ex.what(); - } - } -} // namespace std - -namespace stdexec::__rcvrs { - inline std::string to_string(set_value_t) noexcept { - return {"set_value"}; - } - inline std::string to_string(set_error_t) noexcept { - return {"set_error"}; - } - inline std::string to_string(set_stopped_t) noexcept { - return {"set_stopped"}; - } -} // namespace stdexec::__rcvrs - -namespace exec::__sequence_sender { - inline std::string to_string(set_next_t) noexcept { - return {"set_next"}; - } -} // namespace exec::__sequence_sender - namespace Catch { template struct StringMaker> { From f8841c58c2dc74685bbec1198fb45d07e3a4e331 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 10:52:45 -0700 Subject: [PATCH 35/39] fix bad merge --- include/exec/sequence_senders.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 77a94b183..45584a419 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -859,8 +859,8 @@ namespace exec { } }; - template , class _Sequence> - void __debug_sequence_sender(_Sequence&& __sequence, const _Env& = {}) { + template + void __debug_sequence_sender(_Sequence&& __sequence, const _Env&) { if constexpr (!__is_debug_env<_Env>) { if constexpr (sequence_sender_in<_Sequence, _Env>) { using __sigs_t = stdexec::__completion_signatures_of_t<_Sequence, __debug_env_t<_Env>>; From 2b48dfe3fc76a1189eb541aa2d76ea47d9dafec9 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 16:22:02 -0700 Subject: [PATCH 36/39] how did this ever compile? --- include/exec/sequence/merge_each.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/exec/sequence/merge_each.hpp b/include/exec/sequence/merge_each.hpp index a379a818e..13a021192 100644 --- a/include/exec/sequence/merge_each.hpp +++ b/include/exec/sequence/merge_each.hpp @@ -434,7 +434,7 @@ namespace exec { && __nothrow_connectable<__error_next_sender_t, __error_next_receiver_t>) { auto __next_sender = exec::set_next(__receiver_, __error_sender_t{this}); auto __next_receiver = __error_next_receiver_t{ - this, __env_fn{&__receiver_, &__nested_stop_} + this, __env_fn_t{&__receiver_, &__nested_stop_} }; __error_op_.__emplace_from([&]() { return stdexec::connect( @@ -446,7 +446,7 @@ namespace exec { STDEXEC_TRY { auto __next_sender = exec::set_next(__receiver_, __error_sender_t{this}); auto __next_receiver = __error_next_receiver_t{ - this, __env_fn{&__receiver_, &__nested_stop_} + this, __env_fn_t{&__receiver_, &__nested_stop_} }; __error_op_.__emplace_from([&]() { return stdexec::connect( From ce05c8f95e305337a5fa083120fba356fc1c394c Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 16:32:20 -0700 Subject: [PATCH 37/39] remove checks in multithreaded tests that cause test failures on slow CI runners --- test/exec/sequence/test_merge_each_threaded.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/exec/sequence/test_merge_each_threaded.cpp b/test/exec/sequence/test_merge_each_threaded.cpp index e3cb4b80a..b19fc804a 100644 --- a/test/exec/sequence/test_merge_each_threaded.cpp +++ b/test/exec/sequence/test_merge_each_threaded.cpp @@ -291,9 +291,8 @@ namespace { return count; })))); + CAPTURE(count < 40, count > 4); CHECK(v.has_value() == false); - CHECK(count < 40); - CHECK(count > 4); } TEST_CASE( @@ -364,9 +363,8 @@ namespace { return count; })))); + CAPTURE(count < 40, count > 4); CHECK(v.has_value() == false); - CHECK(count < 40); - CHECK(count > 4); } #endif // STDEXEC_HAS_STD_RANGES() From 850a25cabe015a167cd8c2d35588f3fa3f37c2f3 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Sat, 1 Nov 2025 17:46:38 -0700 Subject: [PATCH 38/39] fixes warnings and errors in clang16 --- include/exec/sequence/marbles.hpp | 24 ++++++++++++++---------- include/exec/sequence/test_scheduler.hpp | 14 +++++++------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/include/exec/sequence/marbles.hpp b/include/exec/sequence/marbles.hpp index 338c00cfb..b2d058b75 100644 --- a/include/exec/sequence/marbles.hpp +++ b/include/exec/sequence/marbles.hpp @@ -431,14 +431,15 @@ namespace exec { using __frame_t = typename _Clock::time_point; using __duration_t = typename _Clock::duration; - constexpr auto __make_span = [](__mstring<_LenB>&& __string) noexcept { - return std::span{__string.__what_, _LenB - 1}; - }; + constexpr auto __make_span = + [](const __mstring<_LenB>& __string) noexcept { + return std::span{__string.__what_, _LenB - 1}; + }; std::vector> __marbles; __frame_t __group_start_frame{-1ms}; __frame_t __frame = __clock.now(); - auto __whole = __make_span(std::move(__diagram)); + auto __whole = __make_span(__diagram); auto __remaining = __whole; auto __consume_first = [&__remaining](std::size_t __skip) noexcept { __remaining = __remaining.subspan(__skip); @@ -531,15 +532,18 @@ namespace exec { && __all_digits) { auto __to_consume = __suffix_begin - __remaining.begin(); long __duration = std::atol(__remaining.data()); - if (std::ranges::equal( - __remaining.subspan(__to_consume, 3), __make_span("ms "_mstr))) { + const auto __ms_str = "ms "_mstr; + const auto __ms = __make_span(__ms_str); + const auto __s_str = "s "_mstr; + const auto __s = __make_span(__s_str); + const auto __m_str = "m "_mstr; + const auto __m = __make_span(__m_str); + if (std::ranges::equal(__remaining.subspan(__to_consume, 3), __ms)) { __to_consume += 2; - } else if (std::ranges::equal( - __remaining.subspan(__to_consume, 2), __make_span("s "_mstr))) { + } else if (std::ranges::equal(__remaining.subspan(__to_consume, 2), __s)) { __duration *= 1000; __to_consume += 1; - } else if (std::ranges::equal( - __remaining.subspan(__to_consume, 2), __make_span("m "_mstr))) { + } else if (std::ranges::equal(__remaining.subspan(__to_consume, 2), __m)) { __duration = __duration * 1000 * 60; __to_consume += 1; } else { diff --git a/include/exec/sequence/test_scheduler.hpp b/include/exec/sequence/test_scheduler.hpp index c5a69fd2a..406e42e39 100644 --- a/include/exec/sequence/test_scheduler.hpp +++ b/include/exec/sequence/test_scheduler.hpp @@ -826,7 +826,7 @@ namespace exec { exec::schedule_at(__self.__context_->get_scheduler(), __marble.frame()), static_cast<_Completion&&>(__completion)), stdexec::prop{stdexec::get_stop_token, __self.__stop_source_.get_token()}) - | stdexec::upon_error([](auto&&) noexcept {}) | stdexec::upon_stopped([]() noexcept {}) + | stdexec::upon_error([](auto&&) noexcept { }) | stdexec::upon_stopped([]() noexcept { }) | stdexec::then([&__self, &__marble]() noexcept { // after each completion, update the __test_sequence_operation_part state STDEXEC_ASSERT(__self.__active_ops_ > 0); @@ -869,10 +869,10 @@ namespace exec { decltype(__schedule_at(__self, __marble, stdexec::__declval<__next_t>())); using __end_sender_t = decltype(__schedule_at(__self, __marble, stdexec::just())); struct __next_sender_id { - using __t = __next_sender_t; + using __t [[maybe_unused]] = __next_sender_t; }; struct __end_sender_id { - using __t = __end_sender_t; + using __t [[maybe_unused]] = __end_sender_t; }; // WORKAROUND clang 19 would fail to compile the construction of the variant_sender. @@ -919,14 +919,14 @@ namespace exec { using __id = __test_sequence; using sender_concept = exec::sequence_sender_t; - using marble_t = marble_t; - using marble_sender_t = marble_t::__marble_sender_t; + using __marble_t = marble_t; + using __marble_sender_t = __marble_t::__marble_sender_t; test_context* __context_; - std::vector __marbles_; + std::vector<__marble_t> __marbles_; template _Self, class... _Env> - static auto get_item_types(_Self&&, _Env&&...) noexcept -> item_types { + static auto get_item_types(_Self&&, _Env&&...) noexcept -> item_types<__marble_sender_t> { return {}; } From 50d6ce7a007753715a6c51f1dd3f2be2a1f3f256 Mon Sep 17 00:00:00 2001 From: Kirk Shoop Date: Thu, 6 Nov 2025 13:30:06 -0800 Subject: [PATCH 39/39] get nvhpc compiling --- include/exec/sequence/merge_each.hpp | 239 +++++++++++------- include/exec/sequence_senders.hpp | 77 ++++-- test/exec/sequence/test_merge_each.cpp | 1 - .../sequence/test_merge_each_threaded.cpp | 68 +++-- 4 files changed, 235 insertions(+), 150 deletions(-) diff --git a/include/exec/sequence/merge_each.hpp b/include/exec/sequence/merge_each.hpp index 13a021192..4739162f8 100644 --- a/include/exec/sequence/merge_each.hpp +++ b/include/exec/sequence/merge_each.hpp @@ -260,6 +260,10 @@ namespace exec { }; }; + template + using __error_sender_t = + __minvoke<__mtry_q, __minvoke<__mtry_q<__error_sender>, _ErrorStorage>>; + template struct __error_next_receiver { using __t = __error_next_receiver; @@ -305,7 +309,7 @@ namespace exec { using __error_storage_t = _ErrorStorage; using __interface_t = __operation_base_interface<__error_storage_t>; - using __error_sender_t = __t<__error_sender<__error_storage_t>>; + using __error_sender_t = __merge_each::__error_sender_t<__error_storage_t>; using __error_next_sender_t = next_sender_of_t<_Receiver&, __error_sender_t>; using __env_fn_t = __env_fn<_Receiver>; using __error_next_receiver_t = __error_next_receiver<_ErrorStorage, __env_fn_t>; @@ -526,7 +530,12 @@ namespace exec { } }; - template + template < + class _NestedValueSender, + class _NestedValueReceiverId, + class _ErrorStorage, + class _NestedValueOp + > struct __nested_value_op { using _NestedValueReceiver = stdexec::__t<_NestedValueReceiverId>; using __base_t = __nested_value_operation_base<_NestedValueReceiver>; @@ -561,6 +570,22 @@ namespace exec { }; }; + template + using __nested_value_op_t = __minvoke< + __mtry_q, + __minvoke< + __mtry_q<__nested_value_op>, + _NestedValueSender, + _NestedValueReceiverId, + _ErrorStorage, + __minvoke< + __mtry_q, + _NestedValueSender, + __receive_nested_value<_NestedValueReceiverId, _ErrorStorage> + > + > + >; + template struct __nested_value_sender { using __operation_base_interface_t = __operation_base_interface<_ErrorStorage>; @@ -569,11 +594,12 @@ namespace exec { using __id = __nested_value_sender; using sender_concept = stdexec::sender_t; - template - using __nested_value_op_t = - stdexec::__t<__nested_value_op<_NestedValueSender, _NestedValueReceiverId, _ErrorStorage>>; - template - using __receiver = __receive_nested_value<_NestedValueReceiverId, _ErrorStorage>; + template + using __nested_value_op_t = __merge_each::__nested_value_op_t< + _NestedValueSender, + stdexec::__id<_NestedValueReceiver>, + _ErrorStorage + >; _NestedValueSender __nested_value_; __operation_base_interface_t* __op_; @@ -592,19 +618,29 @@ namespace exec { template _Self, receiver _NestedValueReceiver> static auto connect(_Self&& __self, _NestedValueReceiver&& __rcvr) noexcept(__nothrow_constructible_from< - __nested_value_op_t>, + __nested_value_op_t<_NestedValueReceiver>, _NestedValueReceiver, _NestedValueSender, __operation_base_interface_t* - >) -> __nested_value_op_t> { - return { - static_cast<_NestedValueReceiver&&>(__rcvr), - static_cast<_NestedValueSender&&>(__self.__nested_value_), - __self.__op_}; + >) -> __nested_value_op_t<_NestedValueReceiver> { + if constexpr (__ok<__nested_value_op_t<_NestedValueReceiver>>) { + return { + static_cast<_NestedValueReceiver&&>(__rcvr), + static_cast<_NestedValueSender&&>(__self.__nested_value_), + __self.__op_}; + } else { + return {}; + } } }; }; + template + using __nested_value_sender_t = __minvoke< + __mtry_q, + __minvoke<__mtry_q<__nested_value_sender>, _NestedValue, _ErrorStorage> + >; + // // __next_.. is returned from set_next. Unlike the rest of the // receivers here, the completion signals to the next receiver @@ -670,16 +706,18 @@ namespace exec { _OperationBase* __op_; using __error_storage_t = typename _OperationBase::__error_storage_t; - template + + template using __nested_value_sender_t = - stdexec::__t<__nested_value_sender<_NestedValue, __error_storage_t>>; + __merge_each::__nested_value_sender_t<_NestedValue, __error_storage_t>; + - template + template auto set_next(_NestedValue&& __nested_value) noexcept(__nothrow_callable< exec::set_next_t, decltype(__op_->__receiver_), __nested_value_sender_t<_NestedValue> - >) -> next_sender auto { + >) { return exec::set_next( __op_->__receiver_, __nested_value_sender_t<_NestedValue>{ @@ -706,6 +744,9 @@ namespace exec { } }; + template + using __receive_nested_values_t = __minvoke<__mtry_q<__receive_nested_values>, _OperationBase>; + struct _INVALID_ARGUMENT_TO_MERGE_WITH_REQUIRES_A_SEQUENCE_OF_SEQUENCES_ { }; template @@ -737,37 +778,32 @@ namespace exec { template using __checked_eval_t = stdexec::__if_c< __valid_args<_Args...>, - stdexec::__types<_Args...>, + __mconst>, __value_completions_error<_Sequence, _Sender, _Env...> >; template - using __f = __checked_eval_t<_Args...>; + using __f = __minvoke<__checked_eval_t<_Args...>, _Args...>; }; template struct __gather_sequences_t { - template + template using __f = stdexec::__gather_completion_signatures< - stdexec::completion_signatures_of_t<_Sender, _Env...>, + _Completions, stdexec::set_value_t, // if set_value __arg_of_t<_Sequence, _Sender, _Env...>::template __f, // else remove stdexec::__mconst>::__f, // concat to __types result - stdexec::__mtry_q>::template __f>::__f + __mtry>>::template __f >; }; template - using __nested_sequences_from_item_type_t = stdexec::__mapply< - stdexec::__if_c< - stdexec::__mvalid - && stdexec::__mvalid<__gather_sequences_t<_Sequence, _Sender, _Env...>::template __f>, - __gather_sequences_t<_Sequence, _Sender, _Env...>, - __value_completions_error<_Sequence, _Sender, _Env...> - >, + using __nested_sequences_from_item_type_t = stdexec::__minvoke< + __mtry<__gather_sequences_t<_Sequence, _Sender, _Env...>>, stdexec::__completion_signatures_of_t<_Sender, _Env...> >; @@ -775,18 +811,20 @@ namespace exec { struct __nested_sequences_t { template - using __f = stdexec::__mapply< - stdexec::__munique>, + using __f = __mtry_q<__mapply>::__f< + __mtry>>, stdexec::__minvoke< - stdexec::__mconcat>, - __nested_sequences_from_item_type_t<_Sequence, _Senders, _Env...>... + __mtry>>, + __minvoke<__mtry_q<__nested_sequences_from_item_type_t>, _Sequence, _Senders, _Env...>... > >; }; template - using __nested_sequences = - __mapply<__nested_sequences_t<_Sequence, _Env...>, __item_types_of_t<_Sequence, _Env...>>; + using __nested_sequences = __mtry_q<__mapply>::__f< + __mtry<__nested_sequences_t<_Sequence, _Env...>>, + __item_types_of_t<_Sequence, _Env...> + >; // // __all_nested_values extracts the types of all the nested value senders. @@ -803,8 +841,9 @@ namespace exec { }; template - using __all_nested_values = - __mapply<__all_nested_values_t<_Env...>, __nested_sequences<_Sequence, _Env...>>; + using __all_nested_values = __mtry_q< + __mapply + >::__f<__mtry<__all_nested_values_t<_Env...>>, __nested_sequences<_Sequence, _Env...>>; // // __error_types extracts the types of all the errors emitted by all the senders in the list. @@ -817,10 +856,10 @@ namespace exec { }; template - using __error_types = stdexec::__mapply< + using __error_types = __mtry_q<__mapply>::__f< stdexec::__mtransform< __error_types_t<_Env...>, - stdexec::__mconcat> + __mtry>> >, _Senders >; @@ -836,7 +875,7 @@ namespace exec { template using __errors = stdexec::__minvoke< - stdexec::__mconcat>, + __mtry>>, // always include std::exception_ptr stdexec::__types, // include errors from senders of the nested sequences @@ -856,33 +895,29 @@ namespace exec { template using __error_variant = - stdexec::__mapply<__q, __errors<_Sequence, _Env...>>; + __mtry_q<__mapply>::__f<__q, __errors<_Sequence, _Env...>>; // // __nested_values extracts the types of all the nested value senders and // builds the item_types list for the merge_each sequence sender. // - template - using __nested_value_sender_t = - stdexec::__t<__nested_value_sender<_NestedValueSender, _ErrorStorage>>; - template struct __nested_values_t { template - using __f = stdexec::__mapply< - stdexec::__munique>, - stdexec::__types< - __nested_value_sender_t<_AllItems, _ErrorStorage>..., - __t<__error_sender<_ErrorStorage>> + using __f = __mtry_q<__mapply>::__f< + __mtry<__munique>>, + __mtry_q<__types>::__f< + __merge_each::__nested_value_sender_t<_AllItems, _ErrorStorage>..., + __merge_each::__error_sender_t<_ErrorStorage> > >; }; template - using __nested_values = stdexec::__mapply< - __nested_values_t<__error_variant<_Sequence, _Env...>, _Env...>, + using __nested_values = __mtry_q<__mapply>::__f< + __mtry_q<__nested_values_t>::__f<__error_variant<_Sequence, _Env...>, _Env...>, __all_nested_values<_Sequence, _Env...> >; @@ -892,23 +927,31 @@ namespace exec { // template - struct __nested_sequence_op_t { + struct __nested_sequence_op_mfn { template - using __f = subscribe_result_t<_Sequence, __receive_nested_values<_OperationBase>>; + using __f = __minvoke< + __mtry_q, + _Sequence, + __receive_nested_values_t<_OperationBase> + >; }; + template + using __nested_sequence_op_t = __minvoke<__nested_sequence_op_mfn<_OperationBase>, _Sequence>; + template - using __operation_base_t = __operation_base< + using __operation_base_t = __minvoke< + __mtry_q<__operation_base>, _Receiver, __error_variant<_Sequence, __env_with_inplace_stop_token_result_t>> >; template - using __nested_sequence_ops_variant = stdexec::__mapply< - stdexec::__mtransform< - __nested_sequence_op_t<__operation_base_t<_Sequence, _Receiver>>, + using __nested_sequence_ops_variant = __mtry_q<__mapply>::__f< + __mtry>, stdexec::__qq - >, + >>, __merge_each::__compute::__nested_sequences< _Sequence, __env_with_inplace_stop_token_result_t> @@ -937,30 +980,30 @@ namespace exec { template auto set_value(_NestedSequence&& __sequence) noexcept { - using __nested_op_t = - subscribe_result_t<_NestedSequence, __receive_nested_values<_OperationBase>>; + using __receiver_t = __receive_nested_values_t<_OperationBase>; + using __nested_op_t = __compute::__nested_sequence_op_t<_NestedSequence, _OperationBase>; if constexpr ( - __nothrow_subscribable<_NestedSequence, __receive_nested_values<_OperationBase>> + __nothrow_subscribable<_NestedSequence, __receiver_t> && stdexec::__nothrow_constructible_from<_NestedSeqOp, __nested_op_t>) { auto& __nested_seq_op = __next_seq_op_->__nested_seq_op_.emplace_from( - [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { + [](_NestedSequence __sequence, __receiver_t __receiver) noexcept -> __nested_op_t { return subscribe( static_cast<_NestedSequence&&>(__sequence), - static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); + static_cast<__receiver_t&&>(__receiver)); }, static_cast<_NestedSequence&&>(__sequence), - __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); + __receiver_t{__next_seq_op_, __op_}); stdexec::start(__nested_seq_op); } else { STDEXEC_TRY { auto& __nested_seq_op = __next_seq_op_->__nested_seq_op_.emplace_from( - [](_NestedSequence __sequence, __receive_nested_values<_OperationBase> __receiver) { + [](_NestedSequence __sequence, __receiver_t __receiver) -> __nested_op_t { return subscribe( static_cast<_NestedSequence&&>(__sequence), - static_cast<__receive_nested_values<_OperationBase>&&>(__receiver)); + static_cast<__receiver_t&&>(__receiver)); }, static_cast<_NestedSequence&&>(__sequence), - __receive_nested_values<_OperationBase>{__next_seq_op_, __op_}); + __receiver_t{__next_seq_op_, __op_}); stdexec::start(__nested_seq_op); } STDEXEC_CATCH_ALL { @@ -1107,14 +1150,10 @@ namespace exec { } }; - - template + template struct __operation { using _Receiver = stdexec::__t<_ReceiverId>; - using __error_storage_t = __compute::__error_variant< - _Sequence, - __env_with_inplace_stop_token_result_t> - >; + using __error_storage_t = _ErrorStorage; using __base_t = __operation_base<_Receiver, __error_storage_t>; struct __t : __base_t { using __id = __operation; @@ -1146,30 +1185,37 @@ namespace exec { }; }; + template + using __operation_t = __minvoke< + __mtry_q<__t>, + __minvoke< + __mtry_q<__operation>, + __id<_Receiver>, + _Sequence, + __compute::__error_variant< + _Sequence, + __env_with_inplace_stop_token_result_t> + > + > + >; + + template struct __subscribe_fn { _Receiver& __rcvr_; template - auto operator()(__ignore, __ignore, _Sequence __sequence) - noexcept(__nothrow_constructible_from< - __t<__operation<__id<_Receiver>, _Sequence>>, - _Receiver, - _Sequence - >) -> __t<__operation<__id<_Receiver>, _Sequence>> { - return {static_cast<_Receiver&&>(__rcvr_), static_cast<_Sequence&&>(__sequence)}; + auto operator()(__ignore, __ignore, _Sequence __sequence) noexcept( + __nothrow_constructible_from<__operation_t<_Receiver, _Sequence>, _Receiver, _Sequence>) + -> __operation_t<_Receiver, _Sequence> { + if constexpr (__ok<__operation_t<_Receiver, _Sequence>>) { + return {static_cast<_Receiver&&>(__rcvr_), static_cast<_Sequence&&>(__sequence)}; + } else { + return {}; + } } }; - struct _INVALID_ARGUMENTS_TO_MERGE_EACH_ { }; - - template - using __argument_error_t = __mexception< - _INVALID_ARGUMENTS_TO_MERGE_EACH_, - _WITH_SEQUENCE_<__child_of<_Self>>, - _WITH_ENVIRONMENT_<_Env>... - >; - // // merge_each is a sequence adaptor that takes a sequence of nested // sequences and merges all the nested values from all the nested @@ -1196,8 +1242,7 @@ namespace exec { template _Self, class... _Env> static auto get_item_types(_Self&&, _Env&&...) noexcept { - return __minvoke< - __mtry_catch<__q<__compute::__nested_values>, __q<__argument_error_t>>, + return __compute::__nested_values< __child_of<_Self>, __env_with_inplace_stop_token_result_t<_Env...> >(); @@ -1207,8 +1252,8 @@ namespace exec { struct __completions_t { template - using __f = __meval< - __concat_completion_signatures, + using __f = __minvoke< + __mtry_q<__concat_completion_signatures>, completion_signatures, completion_signatures_of_t<__child_of<_Self>, _Env>, completion_signatures_of_t<_Sequences, _Env>... @@ -1216,15 +1261,15 @@ namespace exec { }; template - using __completions = __mapply< - __completions_t<_Self, _Env...>, + using __completions = __mtry_q<__mapply>::__f< + __mtry<__completions_t<_Self, _Env...>>, __compute::__nested_sequences<__child_of<_Self>, _Env...> >; template _Self, class... _Env> static auto get_completion_signatures(_Self&&, _Env&&...) noexcept { return __minvoke< - __mtry_catch<__q<__completions>, __q<__argument_error_t>>, + __mtry_q<__completions>, _Self, __env_with_inplace_stop_token_result_t<_Env...> >{}; diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 45584a419..5fc2d77fa 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -21,6 +21,54 @@ #include "stdexec/__detail/__meta.hpp" #include "stdexec/__detail/__diagnostics.hpp" +//////////////////////////////////////////////////////////////////////////////// +#define STDEXEC_ERROR_SEQUENCE_SENDER_DEFINITION \ + "A sequence sender must provide a `subscribe` member function that takes a receiver as an\n" \ + "argument and returns an object whose type satisfies `stdexec::operation_state`,\n" \ + "as shown below:\n" \ + "\n" \ + " class MySequenceSender\n" \ + " {\n" \ + " public:\n" \ + " using sender_concept = exec::sequence_sender_t;\n" \ + " using item_types = exec::item_types<>;\n" \ + " using completion_signatures = stdexec::completion_signatures;\n" \ + "\n" \ + " template \n" \ + " struct MyOpState\n" \ + " {\n" \ + " using operation_state_concept = stdexec::operation_state_t;\n" \ + "\n" \ + " void start() noexcept\n" \ + " {\n" \ + " // Start the operation, which will eventually complete and send its\n" \ + " // result to rcvr_;\n" \ + " }\n" \ + "\n" \ + " Receiver rcvr_;\n" \ + " };\n" \ + "\n" \ + " template \n" \ + " auto subscribe(Receiver rcvr) -> MyOpState\n" \ + " {\n" \ + " return MyOpState{std::move(rcvr)};\n" \ + " }\n" \ + "\n" \ + " ...\n" \ + " };\n" + +//////////////////////////////////////////////////////////////////////////////// +#define STDEXEC_ERROR_CANNOT_SUBSCRIBE_SEQUENCE_TO_RECEIVER \ + "\n" \ + "FAILURE: No usable subscribe customization was found.\n" \ + "\n" STDEXEC_ERROR_SEQUENCE_SENDER_DEFINITION + +//////////////////////////////////////////////////////////////////////////////// +#define STDEXEC_ERROR_SUBSCRIBE_DOES_NOT_RETURN_OPERATION_STATE \ + "\n" \ + "FAILURE: The subscribe customization did not return an `stdexec::operation_state`.\n" \ + "\n" STDEXEC_ERROR_SEQUENCE_SENDER_DEFINITION + namespace exec { namespace __errs { template @@ -536,6 +584,12 @@ namespace exec { template using __tfx_sequence_t = __tfx_sequence_t<_Sequence, env_of_t<_Receiver>>; + template + static constexpr void __check_operation_state() noexcept { + static_assert( + operation_state<_OpState>, STDEXEC_ERROR_SUBSCRIBE_DOES_NOT_RETURN_OPERATION_STATE); + } + template static constexpr auto __select_impl() noexcept { using __domain_t = __late_domain_of_t<_Sequence, env_of_t<_Receiver>>; @@ -557,10 +611,7 @@ namespace exec { next_sender_of_t<_Receiver, __tfx_sequence_t>, __stopped_means_break_t<_Receiver> >; - static_assert( - operation_state<__result_t>, - "stdexec::connect(sender, receiver) must return a type that " - "satisfies the operation_state concept"); + __check_operation_state<__result_t>(); constexpr bool _Nothrow = __nothrow_connectable< next_sender_of_t<_Receiver, __tfx_sequence_t>, __stopped_means_break_t<_Receiver> @@ -569,10 +620,7 @@ namespace exec { } else if constexpr (__subscribable_with_static_member<__tfx_sequence_t, _Receiver>) { using __result_t = decltype(STDEXEC_REMOVE_REFERENCE( __tfx_sequence_t)::subscribe(__declval<__tfx_sequence_t>(), __declval<_Receiver>())); - static_assert( - operation_state<__result_t>, - "Sequence::subscribe(sender, receiver) must return a type that " - "satisfies the operation_state concept"); + __check_operation_state<__result_t>(); constexpr bool _Nothrow = _NothrowTfxSequence && noexcept(STDEXEC_REMOVE_REFERENCE(__tfx_sequence_t)::subscribe( __declval<__tfx_sequence_t>(), __declval<_Receiver>())); @@ -580,20 +628,14 @@ namespace exec { } else if constexpr (__subscribable_with_member<__tfx_sequence_t, _Receiver>) { using __result_t = decltype(__declval<__tfx_sequence_t>() .subscribe(__declval<_Receiver>())); - static_assert( - operation_state<__result_t>, - "Sequence::subscribe(sender, receiver) must return a type that " - "satisfies the operation_state concept"); + __check_operation_state<__result_t>(); constexpr bool _Nothrow = _NothrowTfxSequence && noexcept(__declval<__tfx_sequence_t>() .subscribe(__declval<_Receiver>())); return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); } else if constexpr (__subscribable_with_tag_invoke<__tfx_sequence_t, _Receiver>) { using __result_t = tag_invoke_result_t; - static_assert( - operation_state<__result_t>, - "exec::subscribe(sender, receiver) must return a type that " - "satisfies the operation_state concept"); + __check_operation_state<__result_t>(); constexpr bool _Nothrow = _NothrowTfxSequence && nothrow_tag_invocable; return static_cast<__result_t (*)() noexcept(_Nothrow)>(nullptr); @@ -601,6 +643,9 @@ namespace exec { using __result_t = __debug::__debug_operation; return static_cast<__result_t (*)() noexcept(_NothrowTfxSequence)>(nullptr); } else { + static_assert( + __subscribable_with_static_member<__tfx_sequence_t, _Receiver>, + STDEXEC_ERROR_CANNOT_SUBSCRIBE_SEQUENCE_TO_RECEIVER); return _NO_USABLE_SUBSCRIBE_CUSTOMIZATION_FOUND_(); } } diff --git a/test/exec/sequence/test_merge_each.cpp b/test/exec/sequence/test_merge_each.cpp index f76306a1f..cffbb1eec 100644 --- a/test/exec/sequence/test_merge_each.cpp +++ b/test/exec/sequence/test_merge_each.cpp @@ -211,7 +211,6 @@ namespace { auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok>); STATIC_REQUIRE(ex::__ok>); diff --git a/test/exec/sequence/test_merge_each_threaded.cpp b/test/exec/sequence/test_merge_each_threaded.cpp index b19fc804a..5624638cd 100644 --- a/test/exec/sequence/test_merge_each_threaded.cpp +++ b/test/exec/sequence/test_merge_each_threaded.cpp @@ -98,19 +98,17 @@ namespace { // a sequence adaptor that schedules each item to complete // on the specified scheduler after the specified duration [[maybe_unused]] - static constexpr auto delays_each_on = - [](Sched sched, duration_of_t after) noexcept { - auto delay_value = [](Value&& value, Sched sched, duration_of_t after) { - return sequence(schedule_after(sched, after), static_cast(value)); - }; - auto delay_adaptor = - stdexec::__binder_back>{ - {sched, after}, - {}, - {} - }; - return exec::transform_each(delay_adaptor); + static constexpr auto delays_each_on = [](Sched sched, auto after) noexcept { + auto delay_value = [](Value&& value, Sched sched, auto after) { + return sequence(schedule_after(sched, after), static_cast(value)); }; + auto delay_adaptor = stdexec::__binder_back{ + {sched, after}, + {}, + {} + }; + return exec::transform_each(delay_adaptor); + }; // a sequence adaptor that applies a function to each item // the function must produce a sequence // all the sequences returned from the function are merged @@ -238,7 +236,7 @@ namespace { return duration_cast(now(sched1) - origin).count(); }; - auto stop_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after) { + auto stop_after_on = [sched0, elapsed_ms](auto sched, auto after) { return schedule_after(sched, after) | stdexec::continues_on(sched0) // serializes output on the sched0 strand | stdexec::let_value([elapsed_ms]() noexcept { @@ -249,17 +247,16 @@ namespace { }); }; - auto error_after_on = - [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { - return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms, error]() noexcept { - UNSCOPED_INFO( - error.what() << " - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); - }; + auto error_after_on = [sched0, elapsed_ms](auto sched, auto after, auto error) { + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms, error]() noexcept { + UNSCOPED_INFO( + error.what() << " - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); + }; // a sequence whose items are sequences auto sequences = merge( @@ -310,7 +307,7 @@ namespace { return duration_cast(now(sched1) - origin).count(); }; - auto stop_after_on = [sched0, elapsed_ms](auto sched, duration_of_t after) { + auto stop_after_on = [sched0, elapsed_ms](auto sched, auto after) { return schedule_after(sched, after) | stdexec::continues_on(sched0) // serializes output on the sched0 strand | stdexec::let_value([elapsed_ms]() noexcept { @@ -321,17 +318,16 @@ namespace { }); }; - auto error_after_on = - [sched0, elapsed_ms](auto sched, duration_of_t after, auto error) { - return schedule_after(sched, after) - | stdexec::continues_on(sched0) // serializes output on the sched0 strand - | stdexec::let_value([elapsed_ms, error]() noexcept { - UNSCOPED_INFO( - error.what() << " - at: " << std::setw(3) << elapsed_ms() - << "ms, on thread id: " << std::this_thread::get_id()); - return ex::just_error(error); - }); - }; + auto error_after_on = [sched0, elapsed_ms](auto sched, auto after, auto error) { + return schedule_after(sched, after) + | stdexec::continues_on(sched0) // serializes output on the sched0 strand + | stdexec::let_value([elapsed_ms, error]() noexcept { + UNSCOPED_INFO( + error.what() << " - at: " << std::setw(3) << elapsed_ms() + << "ms, on thread id: " << std::this_thread::get_id()); + return ex::just_error(error); + }); + }; // a sequence whose items are sequences auto sequences = merge(