From 97959b5efbf5af1f5cc9cc214048511edc24c7da Mon Sep 17 00:00:00 2001 From: Cra3z Date: Fri, 13 Nov 2026 16:14:07 +0800 Subject: [PATCH 1/2] Fix `inlinable_receiver` concept --- docs/overview.md | 2 +- .../execution/detail/inlinable_receiver.hpp | 2 +- .../execution/exec-recv-concepts.test.cpp | 131 +++++++++++++++++- 3 files changed, 130 insertions(+), 5 deletions(-) diff --git a/docs/overview.md b/docs/overview.md index 9e6b0287..695bbedd 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -29,7 +29,7 @@ This section lists the concepts from `std::execution`. inlinable_receiver<Rcvr, Child> The concecpt inlinable_receiver<Rcvr, Child> detects if a receiver of type Rcvr can be obtained from a Child -reference using Rcvr::make_receiver_for(child). It is unspecified if any of the standard library receivers is a inlinable_receiver. +pointer using Rcvr::make_receiver_for(child). It is unspecified if any of the standard library receivers is a inlinable_receiver.
diff --git a/include/beman/execution/detail/inlinable_receiver.hpp b/include/beman/execution/detail/inlinable_receiver.hpp index f0e8c118..673574af 100644 --- a/include/beman/execution/detail/inlinable_receiver.hpp +++ b/include/beman/execution/detail/inlinable_receiver.hpp @@ -23,7 +23,7 @@ namespace beman::execution { template concept inlinable_receiver = ::beman::execution::receiver && requires(ChildOp* child) { { - ::std::remove_cvref_t::make_receiver_for(*child) + ::std::remove_cvref_t::make_receiver_for(child) } noexcept -> ::std::same_as<::std::remove_cvref_t>; }; } // namespace beman::execution diff --git a/tests/beman/execution/exec-recv-concepts.test.cpp b/tests/beman/execution/exec-recv-concepts.test.cpp index 762f6c8b..58ade49c 100644 --- a/tests/beman/execution/exec-recv-concepts.test.cpp +++ b/tests/beman/execution/exec-recv-concepts.test.cpp @@ -166,16 +166,119 @@ auto test_receiver_of() -> void { test_std::set_stopped_t()>>); } +template +struct test_basic_receiver { + using receiver_concept = test_std::receiver_tag; + + template + static auto make_receiver_for(ChildOp* child) noexcept -> test_basic_receiver { + // `ChildOp` is the first non-static data member of `ParentOp`. + const auto address = reinterpret_cast(child); + // NOTE: Actually, it's still UB. Under the current standard, pointer conversion/arithmetic cannot achieve + // this. A truly well-defined approach will likely have to wait until proposals like P3407 are adopted into the + // standard. + return test_basic_receiver{reinterpret_cast(address)}; + } + + template + auto set_value(Args&&... args) noexcept -> void { + test_std::set_value(std::move(parent->get_receiver()), std::forward(args)...); + } + + template + auto set_error(E&& e) noexcept -> void { + test_std::set_error(std::move(parent->get_receiver()), std::forward(e)); + } + + auto set_stopped() noexcept -> void { test_std::set_stopped(std::move(parent->get_receiver())); } + + auto get_env() const noexcept -> Env { return test_std::get_env(parent->get_receiver()); } + + ParentOp* parent; +}; + +template +struct test_basic_state { + using operation_state_concept = test_std::operation_state_tag; + + using receiver_t = test_basic_receiver>; + + using child_op_t = test_std::connect_result_t; + + test_basic_state(Sndr sndr, Rcvr rcvr) + : child_op(test_std::connect(std::move(sndr), receiver_t{this})), rcvr(std::move(rcvr)) {} + + auto start() & noexcept -> void { test_std::start(child_op); } + + auto get_receiver() noexcept -> Rcvr& { return rcvr; } + + child_op_t child_op; + Rcvr rcvr; +}; + +template + requires test_std::inlinable_receiver> +struct test_basic_state { + using operation_state_concept = test_std::operation_state_tag; + + using receiver_t = test_basic_receiver>; + + using child_op_t = test_std::connect_result_t; + + test_basic_state(Sndr sndr, const Rcvr&) : child_op(test_std::connect(std::move(sndr), receiver_t{this})) {} + + auto start() & noexcept -> void { test_std::start(child_op); } + + auto get_receiver() noexcept -> Rcvr { return Rcvr::make_receiver_for(this); } + + child_op_t child_op; +}; + +template +struct test_basic_sender { + using sender_concept = test_std::sender_tag; + Tag tag; + Child child; + + template + requires test_std::sender_in + static consteval auto get_completion_signatures() { + return test_std::get_completion_signatures(); + } + + template + auto connect(Rcvr rcvr) { + return test_basic_state{std::move(child), std::move(rcvr)}; + } +}; + +struct test_adaptor : test_std::sender_adaptor_closure { + template + auto operator()(Sndr sndr) { + return test_basic_sender{*this, std::move(sndr)}; + } +}; + struct inlinable_state {}; struct non_inlinable_state {}; -struct receiver { +struct normal_receiver { using receiver_concept = test_std::receiver_tag; + static auto set_value(const auto&...) noexcept -> void {} + static auto set_error(const auto&) noexcept -> void {} + static auto set_stopped() noexcept -> void {} }; struct inlinable_receiver { using receiver_concept = test_std::receiver_tag; - static auto make_receiver_for(inlinable_state&) noexcept -> inlinable_receiver { return inlinable_receiver{}; } - static auto make_receiver_for(non_inlinable_state&) noexcept -> receiver { return receiver{}; } + static auto make_receiver_for(inlinable_state*) noexcept -> inlinable_receiver { return inlinable_receiver{}; } + static auto make_receiver_for(non_inlinable_state*) noexcept -> normal_receiver { return normal_receiver{}; } + template + static auto make_receiver_for(test_basic_state*) noexcept -> inlinable_receiver { + return inlinable_receiver{}; + } + static auto set_value(const auto&...) noexcept -> void {} + static auto set_error(const auto&) noexcept -> void {} + static auto set_stopped() noexcept -> void {} }; static_assert(test_std::receiver); @@ -183,6 +286,28 @@ auto test_inlinable_receiver() -> void { static_assert(not test_std::inlinable_receiver); static_assert(not test_std::inlinable_receiver); static_assert(test_std::inlinable_receiver); + + auto sndr = test_std::just(42) | test_adaptor{}; + using normal_state = test_std::connect_result_t; + using inlinable_state = test_std::connect_result_t; + static_assert(test_std::operation_state); + static_assert(test_std::operation_state); + + auto nested_sndr = sndr | test_adaptor{}; + using nested_normal_state = test_std::connect_result_t; + using nested_inlinable_state = test_std::connect_result_t; + static_assert(sizeof(normal_state) == sizeof(nested_normal_state)); + static_assert(sizeof(inlinable_state) == sizeof(nested_inlinable_state)); + auto [result] = test_std::sync_wait(nested_sndr).value(); + ASSERT(result == 42); + + auto nested_4_sndr = test_std::just(67) | test_adaptor{} | test_adaptor{} | test_adaptor{} | test_adaptor{}; + using nested_4_normal_state = test_std::connect_result_t; + using nested_4_inlinable_state = test_std::connect_result_t; + static_assert(sizeof(normal_state) == sizeof(nested_4_normal_state)); + static_assert(sizeof(inlinable_state) == sizeof(nested_4_inlinable_state)); + auto [result1] = test_std::sync_wait(nested_4_sndr).value(); + ASSERT(result1 == 67); } } // namespace From d32eb6f2b795db0b564e0c52f4dd84673001929e Mon Sep 17 00:00:00 2001 From: Cra3z <3324654761@qq.com> Date: Fri, 13 Nov 2026 23:22:54 +0800 Subject: [PATCH 2/2] Fix error --- tests/beman/execution/exec-recv-concepts.test.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/beman/execution/exec-recv-concepts.test.cpp b/tests/beman/execution/exec-recv-concepts.test.cpp index 58ade49c..16626a4d 100644 --- a/tests/beman/execution/exec-recv-concepts.test.cpp +++ b/tests/beman/execution/exec-recv-concepts.test.cpp @@ -205,15 +205,17 @@ struct test_basic_state { using child_op_t = test_std::connect_result_t; - test_basic_state(Sndr sndr, Rcvr rcvr) - : child_op(test_std::connect(std::move(sndr), receiver_t{this})), rcvr(std::move(rcvr)) {} + test_basic_state(Sndr sndr, Rcvr rcvr) : rcvr(std::move(rcvr)) { + // The child operation state should be created after the receiver is constructed + ::new (static_cast(storage)) child_op_t(test_std::connect(std::move(sndr), receiver_t{this})); + } - auto start() & noexcept -> void { test_std::start(child_op); } + auto start() & noexcept -> void { test_std::start(*::std::launder(reinterpret_cast(storage))); } auto get_receiver() noexcept -> Rcvr& { return rcvr; } - child_op_t child_op; - Rcvr rcvr; + alignas(child_op_t) std::byte storage[sizeof(child_op_t)]; + Rcvr rcvr; }; template