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..16626a4d 100644
--- a/tests/beman/execution/exec-recv-concepts.test.cpp
+++ b/tests/beman/execution/exec-recv-concepts.test.cpp
@@ -166,16 +166,121 @@ 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) : 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(*::std::launder(reinterpret_cast(storage))); }
+
+ auto get_receiver() noexcept -> Rcvr& { return rcvr; }
+
+ alignas(child_op_t) std::byte storage[sizeof(child_op_t)];
+ 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 +288,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