Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ This section lists the concepts from `std::execution`.
<summary><code>inlinable_receiver&lt;<i>Rcvr</i>, <i>Child</i>&gt;</code></summary>
The concecpt <code>inlinable_receiver&lt;<i>Rcvr</i>, <i>Child</i>&gt;</code> detects if a
receiver of type <code><i>Rcvr</i></code> can be obtained from a <code><i>Child</i></code>
reference using <code><i>Rcvr</i>::make_receiver_for(<i>child</i>)</code>. It is unspecified if any of the standard library receivers is a <code>inlinable_receiver</code>.
pointer using <code><i>Rcvr</i>::make_receiver_for(<i>child</i>)</code>. It is unspecified if any of the standard library receivers is a <code>inlinable_receiver</code>.
</details>

<details>
Expand Down
2 changes: 1 addition & 1 deletion include/beman/execution/detail/inlinable_receiver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace beman::execution {
template <typename Receiver, typename ChildOp>
concept inlinable_receiver = ::beman::execution::receiver<Receiver> && requires(ChildOp* child) {
{
::std::remove_cvref_t<Receiver>::make_receiver_for(*child)
::std::remove_cvref_t<Receiver>::make_receiver_for(child)
} noexcept -> ::std::same_as<::std::remove_cvref_t<Receiver>>;
};
} // namespace beman::execution
Expand Down
133 changes: 130 additions & 3 deletions tests/beman/execution/exec-recv-concepts.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,23 +166,150 @@ auto test_receiver_of() -> void {
test_std::set_stopped_t()>>);
}

template <typename ParentOp, typename Env>
struct test_basic_receiver {
using receiver_concept = test_std::receiver_tag;

template <typename ChildOp>
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<void*>(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<ParentOp*>(address)};
}

template <typename... Args>
auto set_value(Args&&... args) noexcept -> void {
test_std::set_value(std::move(parent->get_receiver()), std::forward<Args>(args)...);
}

template <typename E>
auto set_error(E&& e) noexcept -> void {
test_std::set_error(std::move(parent->get_receiver()), std::forward<E>(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 <typename Sndr, typename Rcvr>
struct test_basic_state {
using operation_state_concept = test_std::operation_state_tag;

using receiver_t = test_basic_receiver<test_basic_state, test_std::env_of_t<Rcvr>>;

using child_op_t = test_std::connect_result_t<Sndr, receiver_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<void*>(storage)) child_op_t(test_std::connect(std::move(sndr), receiver_t{this}));
}

auto start() & noexcept -> void { test_std::start(*::std::launder(reinterpret_cast<child_op_t*>(storage))); }

auto get_receiver() noexcept -> Rcvr& { return rcvr; }

alignas(child_op_t) std::byte storage[sizeof(child_op_t)];
Rcvr rcvr;
};

template <typename Sndr, typename Rcvr>
requires test_std::inlinable_receiver<Rcvr, test_basic_state<Sndr, Rcvr>>
struct test_basic_state<Sndr, Rcvr> {
using operation_state_concept = test_std::operation_state_tag;

using receiver_t = test_basic_receiver<test_basic_state, test_std::env_of_t<Rcvr>>;

using child_op_t = test_std::connect_result_t<Sndr, receiver_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 <typename Tag, typename Child>
struct test_basic_sender {
using sender_concept = test_std::sender_tag;
Tag tag;
Child child;

template <typename, typename... Env>
requires test_std::sender_in<Child, Env...>
static consteval auto get_completion_signatures() {
return test_std::get_completion_signatures<Child, Env...>();
}

template <typename Rcvr>
auto connect(Rcvr rcvr) {
return test_basic_state<Child, Rcvr>{std::move(child), std::move(rcvr)};
}
};

struct test_adaptor : test_std::sender_adaptor_closure<test_adaptor> {
template <typename Sndr>
auto operator()(Sndr sndr) {
return test_basic_sender<test_adaptor, Sndr>{*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 <typename Child, typename Rcvr>
static auto make_receiver_for(test_basic_state<Child, Rcvr>*) 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<inlinable_receiver>);

auto test_inlinable_receiver() -> void {
static_assert(not test_std::inlinable_receiver<inlinable_receiver, int>);
static_assert(not test_std::inlinable_receiver<inlinable_receiver, non_inlinable_state>);
static_assert(test_std::inlinable_receiver<inlinable_receiver, inlinable_state>);

auto sndr = test_std::just(42) | test_adaptor{};
using normal_state = test_std::connect_result_t<decltype(sndr), normal_receiver>;
using inlinable_state = test_std::connect_result_t<decltype(sndr), inlinable_receiver>;
static_assert(test_std::operation_state<normal_state>);
static_assert(test_std::operation_state<inlinable_state>);

auto nested_sndr = sndr | test_adaptor{};
using nested_normal_state = test_std::connect_result_t<decltype(nested_sndr), normal_receiver>;
using nested_inlinable_state = test_std::connect_result_t<decltype(nested_sndr), inlinable_receiver>;
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<decltype(nested_4_sndr), normal_receiver>;
using nested_4_inlinable_state = test_std::connect_result_t<decltype(nested_4_sndr), inlinable_receiver>;
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

Expand Down
Loading