diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 1627a106..4fd8a7fe 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -17,10 +17,10 @@ concurrency: jobs: beman-submodule-check: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.7.4 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.7.5 preset-test: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.7.4 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.7.5 with: matrix_config: > [ @@ -33,7 +33,7 @@ jobs: ] build-and-test: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-build-and-test.yml@1.7.4 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-build-and-test.yml@1.7.5 with: matrix_config: > { @@ -132,4 +132,4 @@ jobs: create-issue-when-fault: needs: [preset-test, build-and-test] if: failure() && github.event_name == 'schedule' - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.7.4 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.7.5 diff --git a/.github/workflows/pre-commit-check.yml b/.github/workflows/pre-commit-check.yml index 85350c4d..3b45a62d 100644 --- a/.github/workflows/pre-commit-check.yml +++ b/.github/workflows/pre-commit-check.yml @@ -17,4 +17,4 @@ permissions: jobs: pre-commit: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.7.4 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.7.5 diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index 440984c3..e604fa48 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -9,7 +9,7 @@ on: jobs: auto-update-pre-commit: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-update-pre-commit.yml@1.7.4 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-update-pre-commit.yml@1.7.5 secrets: APP_ID: ${{ secrets.AUTO_PR_BOT_APP_ID }} PRIVATE_KEY: ${{ secrets.AUTO_PR_BOT_PRIVATE_KEY }} 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..4f65cd13 100644 --- a/tests/beman/execution/exec-recv-concepts.test.cpp +++ b/tests/beman/execution/exec-recv-concepts.test.cpp @@ -166,16 +166,123 @@ 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})); + } + + ~test_basic_state() { std::destroy_at(std::launder(reinterpret_cast(storage))); } + + 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) const noexcept { + 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 +290,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