From 290b7fb29ba76d3dc033cfd2372783d0ef8a3689 Mon Sep 17 00:00:00 2001 From: alwaysprince05 Date: Wed, 30 Sep 2026 00:06:02 +0530 Subject: [PATCH 1/3] fix(exec): make write_env transparent to sequence sender semantics A write_env sender wrapping a sequence sender was not itself a sequence sender: get_item_types has no branch that looks through stdexec sender adaptors, so sequence-aware algorithms like exec::transform_each treated the wrapper as a sequence of one and collapsed the item types. Make write_env senders transparent to sequence semantics: - specialize enable_sequence_sender for __write_env_t sexprs - compute item types of a write_env sender from its child in the joined environment (recursing through stacked wrappers) - subscribe to the child through a receiver that joins the written environment with the wrapped receiver's environment First half of #2053. The starts_on half goes through the continues_on machinery that #2277 is reworking, so it is left for a follow-up. --- include/exec/sequence_senders.hpp | 97 +++++++++++++++++ test/exec/CMakeLists.txt | 1 + .../exec/sequence/test_write_env_sequence.cpp | 101 ++++++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 test/exec/sequence/test_write_env_sequence.cpp diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 5d553ba84..73ec93433 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -222,6 +222,20 @@ namespace experimental::execution template inline constexpr bool enable_sequence_sender = __enable_sequence_sender<_Sequence>; + // Sender adaptors created with `__make_sexpr` whose tag is `__write_env_t` + // are transparent to sequence sender semantics: `write_env` only augments the + // environment seen by its children; it neither adds nor removes sequence + // items. `__get_item_types_helper` below computes the item types of such + // senders from their children, and `subscribe_t` knows how to subscribe to + // them. Without this, wrapping a sequence sender in `write_env` (or in an + // algorithm that uses `write_env` internally) would hide the sequence + // semantics from downstream sequence-aware algorithms. See issue #2053. + template + requires STDEXEC::__same_as< + typename STDEXEC::__desc_of_t>::__tag, + STDEXEC::__write_env_t> + inline constexpr bool enable_sequence_sender> = true; + STDEXEC_MODULE_EXPORT template struct item_types @@ -319,6 +333,16 @@ namespace experimental::execution { return STDEXEC_REMOVE_REFERENCE(_Sequence)::template get_item_types<_Sequence>(); } + else if constexpr (STDEXEC::__sender_for<_Sequence, STDEXEC::__write_env_t>) + { + // A `write_env` sender is transparent as a sequence sender: its item + // types are the item types of its child, computed in the child's + // environment (the written environment joined with the receiver's + // environment). This mirrors how `write_env` computes completion + // signatures from its child's signatures in the joined environment. + using __child_env_t = __join_env_t<__decay_t<__data_of<_Sequence>> const &, _Env...>; + return __get_item_types_helper<__child_of<_Sequence>, __child_env_t>(); + } else if constexpr (sender_in<_Sequence, _Env...> && !enable_sequence_sender>) { @@ -763,6 +787,51 @@ namespace experimental::execution using __subscribe_static_member_result_t = decltype(STDEXEC_REMOVE_REFERENCE( _Sequence)::__static_subscribe(__declval<_Sequence>(), __declval<_Receiver>())); + // Receiver wrapper used to subscribe to the child of a `write_env` sender: + // it forwards all completions and `set_next` calls to the wrapped receiver, + // but presents the written environment joined with (and shadowing) the + // wrapped receiver's environment. This mirrors the environment that + // `write_env`'s operation state presents to its child when connecting. + template + struct __write_env_rcvr + { + using receiver_concept = STDEXEC::receiver_tag; + + template + auto set_next(_Item&& __item) & noexcept(__nothrow_callable) + -> next_sender_of_t<_Receiver, _Item> + { + return exec::set_next(__rcvr_, static_cast<_Item&&>(__item)); + } + + void set_value() noexcept + { + STDEXEC::set_value(static_cast<_Receiver&&>(__rcvr_)); + } + + template + void set_error(_Error&& __error) noexcept + { + STDEXEC::set_error(static_cast<_Receiver&&>(__rcvr_), static_cast<_Error&&>(__error)); + } + + void set_stopped() noexcept + requires __callable + { + STDEXEC::set_stopped(static_cast<_Receiver&&>(__rcvr_)); + } + + auto get_env() const noexcept -> __join_env_t<_Env const &, env_of_t<_Receiver>> + { + return __env::__join(__env_, STDEXEC::get_env(__rcvr_)); + } + + STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS + _Receiver __rcvr_; + STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS + _Env __env_; + }; + template concept __subscribable_with_static_member = __minvocable_q<__subscribe_static_member_result_t, _Sequence, _Receiver>; @@ -831,6 +900,20 @@ namespace experimental::execution __stopped_means_break<_Receiver>>; return __declfn<__result_t, __nothrow_subscribe && __nothrow_tfx_seq>(); } + else if constexpr (STDEXEC::__sender_for<__tfx_seq_t, STDEXEC::__write_env_t>) + { + // A `write_env` sender is transparent to sequence sender semantics: + // subscribe to its child with a receiver that presents the written + // environment (joined with, and shadowing, the receiver's own + // environment). See issue #2053. + using __child_t = __child_of<__tfx_seq_t>; + using __written_t = __decay_t<__data_of<__tfx_seq_t>>; + using __rcvr_t = __write_env_rcvr<_Receiver, __written_t>; + using __result_t = STDEXEC::__call_result_t; + __check_operation_state<__result_t>(); + constexpr bool __nothrow_subscribe = __nothrow_callable; + return __declfn<__result_t, __nothrow_subscribe && __nothrow_tfx_seq>(); + } else if constexpr (__subscribable_with_static_member<__tfx_seq_t, _Receiver>) { using __result_t = decltype(STDEXEC_REMOVE_REFERENCE( @@ -884,6 +967,20 @@ namespace experimental::execution __stopped_means_break<_Receiver>{ static_cast<_Receiver&&>(__rcvr)}); } + else if constexpr (STDEXEC::__sender_for<__tfx_seq_t, STDEXEC::__write_env_t>) + { + // `write_env` senders are transparent to sequence sender semantics; + // see the matching branch in __get_declfn above and issue #2053. + auto& [__tag, __env, __child] = __tfx_seq; + (void) __tag; + using __written_t = __decay_t; + // Recurse into the CPO with the unwrapped child; each level of + // `write_env` nesting is unwrapped one at a time. + return (*this)(STDEXEC::__forward_like<__tfx_seq_t>(__child), + __write_env_rcvr<_Receiver, __written_t>{ + static_cast<_Receiver&&>(__rcvr), + STDEXEC::__forward_like(__env)}); + } else if constexpr (__subscribable_with_static_member<__tfx_seq_t, _Receiver>) { // NOLINT(bugprone-branch-clone) return __tfx_seq.__static_subscribe(static_cast<__tfx_seq_t&&>(__tfx_seq), diff --git a/test/exec/CMakeLists.txt b/test/exec/CMakeLists.txt index 667bdedb6..8b24f960b 100644 --- a/test/exec/CMakeLists.txt +++ b/test/exec/CMakeLists.txt @@ -57,6 +57,7 @@ set(exec_test_sources sequence/test_empty_sequence.cpp sequence/test_ignore_all_values.cpp sequence/test_iterate.cpp + sequence/test_write_env_sequence.cpp sequence/test_transform_each.cpp sequence/test_merge.cpp sequence/test_sequence_any_sender.cpp diff --git a/test/exec/sequence/test_write_env_sequence.cpp b/test/exec/sequence/test_write_env_sequence.cpp new file mode 100644 index 000000000..7d9374b81 --- /dev/null +++ b/test/exec/sequence/test_write_env_sequence.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2026 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. + */ + +// Regression tests for issue #2053: `write_env` must be transparent to +// sequence sender semantics. Wrapping a sequence sender in `write_env` (or in +// several stacked `write_env` layers) must preserve the item types, and items +// must flow through the wrapper unchanged at runtime. + +#include "exec/sequence/transform_each.hpp" + +#include "exec/sender_for.hpp" +#include "exec/sequence/ignore_all_values.hpp" +#include "exec/sequence_senders.hpp" +#include + +#include +#include +#include + +namespace +{ + + // A minimal sequence sender that produces a single `just(42)` item. + template + struct single_item_sequence + { + using sender_concept = exec::sequence_sender_tag; + using item_types = exec::item_types; + using completion_signatures = + STDEXEC::completion_signatures; + + template + struct op + { + using operation_state_concept = STDEXEC::operation_state_t; + Rcvr rcvr_; + + void start() noexcept + { + auto next = exec::set_next(rcvr_, STDEXEC::just(Value)); + auto item_op = STDEXEC::connect(std::move(next), std::move(rcvr_)); + STDEXEC::start(item_op); + } + }; + + template + auto subscribe(Rcvr rcvr) const -> op + { + return op{static_cast(rcvr)}; + } + }; + + TEST_CASE("write_env is transparent to sequence senders - item types", "[sequence][write_env]") + { + using wrapper_t = STDEXEC::__decay_t{}, + STDEXEC::env<>{}))>; + using items_t = decltype(exec::get_item_types>()); + static_assert(STDEXEC::__same_as>); + } + + TEST_CASE("stacked write_env is transparent to sequence senders", "[sequence][write_env]") + { + using wrapper_t = STDEXEC::__decay_t< + decltype(STDEXEC::write_env(STDEXEC::write_env(single_item_sequence<42>{}, STDEXEC::env<>{}), + STDEXEC::env<>{}))>; + using items_t = decltype(exec::get_item_types>()); + static_assert(STDEXEC::__same_as>); + } + + TEST_CASE("write_env preserves sequence item flow", "[sequence][write_env]") + { + int value = 0; + STDEXEC::sync_wait(STDEXEC::write_env(single_item_sequence<42>{}, STDEXEC::env<>{}) + | exec::transform_each(STDEXEC::then([&](int v) { value = v; })) + | exec::ignore_all_values()); + CHECK(value == 42); + } + + TEST_CASE("stacked write_env preserves sequence item flow", "[sequence][write_env]") + { + int value = 0; + STDEXEC::sync_wait( + STDEXEC::write_env(STDEXEC::write_env(single_item_sequence<42>{}, STDEXEC::env<>{}), + STDEXEC::env<>{}) + | exec::transform_each(STDEXEC::then([&](int v) { value = v; })) | exec::ignore_all_values()); + CHECK(value == 42); + } +} // namespace From 3b2abb063c151eea3aa7f388fbb15fe94e46c65b Mon Sep 17 00:00:00 2001 From: alwaysprince05 Date: Thu, 1 Oct 2026 02:20:39 +0530 Subject: [PATCH 2/3] fix(exec): make sequence-adaptor transparency an open customization point Review feedback on #2294: get_item_types and subscribe must not hard-code individual sender adaptors, since the set of adaptors is open. Replace the write_env-specific branches with a generic mechanism driven by a new __sequence_adaptor_traits customization point: an adaptor that forwards set_next and all completions to a single child declares itself transparent for its tag, optionally naming the environment its child observes. write_env now only specializes that trait, and the sequence machinery handles every transparent adaptor the same way. The enable_sequence_sender specialization also goes through tag_of_t rather than digging into the sender's descriptor. Also merge upstream/main, and extend the regression tests with a user-defined adaptor that opts in via the new trait, to show that the extension point is open. --- include/exec/sequence_senders.hpp | 186 ++++++++++++++---- .../exec/sequence/test_write_env_sequence.cpp | 56 +++++- 2 files changed, 197 insertions(+), 45 deletions(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 73ec93433..9d0bbbfb5 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -222,18 +222,98 @@ namespace experimental::execution template inline constexpr bool enable_sequence_sender = __enable_sequence_sender<_Sequence>; - // Sender adaptors created with `__make_sexpr` whose tag is `__write_env_t` - // are transparent to sequence sender semantics: `write_env` only augments the - // environment seen by its children; it neither adds nor removes sequence - // items. `__get_item_types_helper` below computes the item types of such - // senders from their children, and `subscribe_t` knows how to subscribe to - // them. Without this, wrapping a sequence sender in `write_env` (or in an - // algorithm that uses `write_env` internally) would hide the sequence - // semantics from downstream sequence-aware algorithms. See issue #2053. + // A sequence-adaptor traits customization point. The set of sender adaptors + // is open, so the sequence machinery cannot know about them individually. + // Instead, an adaptor that is transparent to sequence semantics — one that + // forwards `set_next` and all completions through to a single child sender, + // possibly adjusting the environment that child sees — declares that here, + // and `get_item_types` and `subscribe` handle such senders generically. See + // issue #2053. + template + struct __sequence_adaptor_traits + { + // Adaptors default to opaque: a wrapper around a sequence sender is not + // itself a sequence sender. + static constexpr bool __transparent = false; + }; + + // The default child-environment transformation for transparent adaptors: + // the child is queried in the same environment as the adaptor itself. + struct __identity_child_env_fn + { + template + auto operator()(_Env __env, _Data const &) const noexcept -> _Env + { + return __env; + } + }; + + // `write_env` only augments the environment seen by its children; it + // neither adds nor removes sequence items, and it computes completion + // signatures from its child's signatures in the joined environment. Mark it + // transparent with the matching environment transformation. See issue + // #2053. + template <> + struct __sequence_adaptor_traits + { + static constexpr bool __transparent = true; + + template + struct __child_env_fn + { + auto operator()(_Env __env, _Data const & __data) const noexcept + -> STDEXEC::__join_env_t<_Data const &, _Env> + { + return STDEXEC::__env::__join(__data, static_cast<_Env&&>(__env)); + } + }; + }; + + // True when _Sequence is a sender expression whose adaptor traits (see + // `__sequence_adaptor_traits` above) mark it as transparent to sequence + // sender semantics. + template + concept __transparent_sequence_adaptor = + STDEXEC::__minvocable_q> + && __sequence_adaptor_traits>>::__transparent; + + // The function object that transforms the environment of a transparent + // sequence adaptor: the adaptor's traits may define a nested + // `__child_env_fn<_Env, _Data>` function object type; otherwise, the + // transformation is the identity. + template + consteval auto __adaptor_child_env_fn() + { + if constexpr (requires { + typename __sequence_adaptor_traits<_Tag>::template __child_env_fn<_Env, _Data>; + }) + { + return typename __sequence_adaptor_traits<_Tag>::template __child_env_fn<_Env, _Data>{}; + } + else + { + return __identity_child_env_fn{}; + } + } + + template + using __adaptor_child_env_fn_t = decltype(__adaptor_child_env_fn<_Tag, _Env, _Data>()); + + // The environment in which the child of a transparent sequence adaptor is + // queried, given the adaptor's tag, the environment _Env in which the + // adaptor itself is queried, and the adaptor's data. + template + using __adaptor_child_env_t = + STDEXEC::__call_result_t<__adaptor_child_env_fn_t<_Tag, _Env, _Data>, _Env, _Data const &>; + + // Sender expressions whose adaptor traits mark them transparent (see + // `__sequence_adaptor_traits` above) count as sequence senders: they + // forward `set_next` and all completions through to a single child sender. + // Without this, wrapping a sequence sender in a transparent adaptor (e.g. + // `write_env`) would hide the sequence semantics from downstream + // sequence-aware algorithms. See issue #2053. template - requires STDEXEC::__same_as< - typename STDEXEC::__desc_of_t>::__tag, - STDEXEC::__write_env_t> + requires __transparent_sequence_adaptor> inline constexpr bool enable_sequence_sender> = true; STDEXEC_MODULE_EXPORT @@ -333,15 +413,25 @@ namespace experimental::execution { return STDEXEC_REMOVE_REFERENCE(_Sequence)::template get_item_types<_Sequence>(); } - else if constexpr (STDEXEC::__sender_for<_Sequence, STDEXEC::__write_env_t>) + else if constexpr (__transparent_sequence_adaptor<_Sequence>) { - // A `write_env` sender is transparent as a sequence sender: its item - // types are the item types of its child, computed in the child's - // environment (the written environment joined with the receiver's - // environment). This mirrors how `write_env` computes completion - // signatures from its child's signatures in the joined environment. - using __child_env_t = __join_env_t<__decay_t<__data_of<_Sequence>> const &, _Env...>; - return __get_item_types_helper<__child_of<_Sequence>, __child_env_t>(); + // A transparent sequence adaptor (see `__sequence_adaptor_traits` + // above) is unwrapped one layer at a time: its item types are the + // item types of its child, computed in the transformed environment + // the adaptor presents to its child. + using __tag_t = STDEXEC::tag_of_t>; + using __data_t = STDEXEC::__decay_t>; + if constexpr (sizeof...(_Env) == 0) + { + return __get_item_types_helper< + STDEXEC::__child_of<_Sequence>, + __adaptor_child_env_t<__tag_t, STDEXEC::env<>, __data_t>>(); + } + else + { + return __get_item_types_helper, + __adaptor_child_env_t<__tag_t, _Env, __data_t>...>(); + } } else if constexpr (sender_in<_Sequence, _Env...> && !enable_sequence_sender>) @@ -787,13 +877,15 @@ namespace experimental::execution using __subscribe_static_member_result_t = decltype(STDEXEC_REMOVE_REFERENCE( _Sequence)::__static_subscribe(__declval<_Sequence>(), __declval<_Receiver>())); - // Receiver wrapper used to subscribe to the child of a `write_env` sender: - // it forwards all completions and `set_next` calls to the wrapped receiver, - // but presents the written environment joined with (and shadowing) the - // wrapped receiver's environment. This mirrors the environment that - // `write_env`'s operation state presents to its child when connecting. + // Receiver wrapper used to subscribe to the child of a transparent + // sequence adaptor (see `__sequence_adaptor_traits` above): it forwards + // all completions and `set_next` calls to the wrapped receiver, but + // presents the adaptor's transformed environment joined with (and + // shadowing) the wrapped receiver's environment. This mirrors the + // environment the adaptor's own operation state would present to its + // child when connecting. template - struct __write_env_rcvr + struct __adaptor_rcvr { using receiver_concept = STDEXEC::receiver_tag; @@ -821,9 +913,9 @@ namespace experimental::execution STDEXEC::set_stopped(static_cast<_Receiver&&>(__rcvr_)); } - auto get_env() const noexcept -> __join_env_t<_Env const &, env_of_t<_Receiver>> + auto get_env() const noexcept -> _Env const & { - return __env::__join(__env_, STDEXEC::get_env(__rcvr_)); + return __env_; } STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS @@ -900,16 +992,19 @@ namespace experimental::execution __stopped_means_break<_Receiver>>; return __declfn<__result_t, __nothrow_subscribe && __nothrow_tfx_seq>(); } - else if constexpr (STDEXEC::__sender_for<__tfx_seq_t, STDEXEC::__write_env_t>) + else if constexpr (__transparent_sequence_adaptor<__tfx_seq_t>) { - // A `write_env` sender is transparent to sequence sender semantics: - // subscribe to its child with a receiver that presents the written + // A transparent sequence adaptor (see `__sequence_adaptor_traits` + // above) is unwrapped one layer at a time: subscribe to its child + // with a receiver that presents the adaptor's transformed // environment (joined with, and shadowing, the receiver's own // environment). See issue #2053. - using __child_t = __child_of<__tfx_seq_t>; - using __written_t = __decay_t<__data_of<__tfx_seq_t>>; - using __rcvr_t = __write_env_rcvr<_Receiver, __written_t>; - using __result_t = STDEXEC::__call_result_t; + using __tag_t = STDEXEC::tag_of_t>; + using __data_t = __decay_t<__data_of<__tfx_seq_t>>; + using __child_t = __child_of<__tfx_seq_t>; + using __child_env_t = __adaptor_child_env_t<__tag_t, env_of_t<_Receiver>, __data_t>; + using __rcvr_t = __adaptor_rcvr<_Receiver, __child_env_t>; + using __result_t = STDEXEC::__call_result_t; __check_operation_state<__result_t>(); constexpr bool __nothrow_subscribe = __nothrow_callable; return __declfn<__result_t, __nothrow_subscribe && __nothrow_tfx_seq>(); @@ -967,19 +1062,24 @@ namespace experimental::execution __stopped_means_break<_Receiver>{ static_cast<_Receiver&&>(__rcvr)}); } - else if constexpr (STDEXEC::__sender_for<__tfx_seq_t, STDEXEC::__write_env_t>) + else if constexpr (__transparent_sequence_adaptor<__tfx_seq_t>) { - // `write_env` senders are transparent to sequence sender semantics; - // see the matching branch in __get_declfn above and issue #2053. - auto& [__tag, __env, __child] = __tfx_seq; + // Transparent sequence adaptors (see `__sequence_adaptor_traits` + // above) are unwrapped one layer at a time; see the matching branch + // in __get_declfn above and issue #2053. + auto& [__tag, __data, __child] = __tfx_seq; (void) __tag; - using __written_t = __decay_t; - // Recurse into the CPO with the unwrapped child; each level of - // `write_env` nesting is unwrapped one at a time. + using __tag_t = STDEXEC::tag_of_t>; + using __data_t = __decay_t; + using __child_env_t = __adaptor_child_env_t<__tag_t, env_of_t<_Receiver>, __data_t>; + using __xform_t = __adaptor_child_env_fn_t<__tag_t, env_of_t<_Receiver>, __data_t>; + // Recurse into the CPO with the unwrapped child; each layer of + // adaptor nesting is unwrapped one at a time. return (*this)(STDEXEC::__forward_like<__tfx_seq_t>(__child), - __write_env_rcvr<_Receiver, __written_t>{ + __adaptor_rcvr<_Receiver, __child_env_t>{ static_cast<_Receiver&&>(__rcvr), - STDEXEC::__forward_like(__env)}); + __xform_t{}(static_cast(__env), + STDEXEC::__forward_like(__data))}); } else if constexpr (__subscribable_with_static_member<__tfx_seq_t, _Receiver>) { // NOLINT(bugprone-branch-clone) diff --git a/test/exec/sequence/test_write_env_sequence.cpp b/test/exec/sequence/test_write_env_sequence.cpp index 7d9374b81..00d642eaf 100644 --- a/test/exec/sequence/test_write_env_sequence.cpp +++ b/test/exec/sequence/test_write_env_sequence.cpp @@ -21,7 +21,6 @@ #include "exec/sequence/transform_each.hpp" -#include "exec/sender_for.hpp" #include "exec/sequence/ignore_all_values.hpp" #include "exec/sequence_senders.hpp" #include @@ -30,9 +29,10 @@ #include #include +#include + namespace { - // A minimal sequence sender that produces a single `just(42)` item. template struct single_item_sequence @@ -98,4 +98,56 @@ namespace | exec::transform_each(STDEXEC::then([&](int v) { value = v; })) | exec::ignore_all_values()); CHECK(value == 42); } + + // A user-defined adaptor, to demonstrate that the transparent-adaptor + // customization point is open: any single-child adaptor can declare itself + // transparent to sequence sender semantics by specializing + // `__sequence_adaptor_traits` for its tag, without any changes to the + // sequence machinery. See issue #2053. + struct my_adapt_t + { }; + + template + auto my_adapt(Sndr&& sndr) + { + return STDEXEC::__make_sexpr(STDEXEC::env<>{}, static_cast(sndr)); + } + + struct my_adapt_impl : STDEXEC::__sexpr_defaults + { + template + static consteval auto __get_completion_signatures() + { + return STDEXEC::get_completion_signatures, Env...>(); + } + }; +} // namespace + +template <> +struct STDEXEC::__sexpr_impl : my_adapt_impl +{ }; + +template <> +struct experimental::execution::__sequence_adaptor_traits +{ + static constexpr bool __transparent = true; +}; + +namespace +{ + TEST_CASE("a custom adaptor can declare itself transparent", "[sequence][write_env]") + { + using wrapper_t = STDEXEC::__decay_t{}))>; + using items_t = decltype(exec::get_item_types>()); + static_assert(STDEXEC::__same_as>); + } + + TEST_CASE("a custom transparent adaptor preserves sequence item flow", "[sequence][write_env]") + { + int value = 0; + STDEXEC::sync_wait(my_adapt(single_item_sequence<42>{}) + | exec::transform_each(STDEXEC::then([&](int v) { value = v; })) + | exec::ignore_all_values()); + CHECK(value == 42); + } } // namespace From fbf50b4b052723291c3f69568aeaef6f50303af5 Mon Sep 17 00:00:00 2001 From: alwaysprince05 Date: Sun, 4 Oct 2026 00:13:01 +0530 Subject: [PATCH 3/3] test: fix clang-format-21 violations in test_write_env_sequence.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empty struct bodies written as `{ };` fail the style CI job, which runs clang-format-21 --dry-run --Werror over all tracked sources. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- test/exec/sequence/test_write_env_sequence.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/exec/sequence/test_write_env_sequence.cpp b/test/exec/sequence/test_write_env_sequence.cpp index 00d642eaf..9b8aea31d 100644 --- a/test/exec/sequence/test_write_env_sequence.cpp +++ b/test/exec/sequence/test_write_env_sequence.cpp @@ -105,7 +105,7 @@ namespace // `__sequence_adaptor_traits` for its tag, without any changes to the // sequence machinery. See issue #2053. struct my_adapt_t - { }; + {}; template auto my_adapt(Sndr&& sndr) @@ -125,7 +125,7 @@ namespace template <> struct STDEXEC::__sexpr_impl : my_adapt_impl -{ }; +{}; template <> struct experimental::execution::__sequence_adaptor_traits