From 27377f754e387a5a0e05e82f4a9e2e9e1c98e00e Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Tue, 26 Nov 2024 10:58:03 -0700 Subject: [PATCH] :sparkles: Add `into_variant` --- docs/sender_adaptors.adoc | 23 ++++ docs/synopsis.adoc | 4 + include/async/into_variant.hpp | 130 +++++++++++++++++++ test/CMakeLists.txt | 1 + test/into_variant.cpp | 222 +++++++++++++++++++++++++++++++++ 5 files changed, 380 insertions(+) create mode 100644 include/async/into_variant.hpp create mode 100644 test/into_variant.cpp diff --git a/docs/sender_adaptors.adoc b/docs/sender_adaptors.adoc index 3b9881e..7e9c8ea 100644 --- a/docs/sender_adaptors.adoc +++ b/docs/sender_adaptors.adoc @@ -63,6 +63,29 @@ NOTE: The incited scheduler must produce a sender which completes asynchronously. A synchronous scheduler would require no incitement, and `continue_on` would be correct. +=== `into_variant` + +Found in the header: `async/into_variant.hpp` + +`into_variant` adapts a sender that has several possible value completions into +a sender with a single value completion that is a variant of tuples, where each +tuple represents one of the original sender's value completions. + +[source,cpp] +---- +auto sndr = async::make_variant_sender( + selection, + [] { return async::just(42, 17); }, + [] { return async::just(2.718f, 3.14f); }) + | async::into_variant(); + +// sndr will complete with set_value(variant, tuple>) +---- + +NOTE: Some sender consumers (like +xref:sender_consumers.adoc#_sync_wait[`sync_wait`]) require the sender to have +one possible value completion. + === `let_error` Found in the header: `async/let_error.hpp` diff --git a/docs/synopsis.adoc b/docs/synopsis.adoc index a142e1f..d805a96 100644 --- a/docs/synopsis.adoc +++ b/docs/synopsis.adoc @@ -61,6 +61,9 @@ in pipe-composition syntax. ==== https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/incite_on.hpp[incite_on.hpp] * `incite_on` - a xref:sender_adaptors.adoc#_incite_on[sender adaptor] that incites execution on another scheduler +==== https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/into_variant.hpp[into_variant.hpp] +* `into_variant` - a xref:sender_adaptors.adoc#_into_variant[sender adaptor] that collapses value completions into a variant + ==== https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/just.hpp[just.hpp] * `just` - a xref:sender_factories.adoc#_just[sender factory] that sends on the value channel * `just_error` - a xref:sender_factories.adoc#_just_error[sender factory] that sends on the error channel @@ -230,6 +233,7 @@ contains traits and metaprogramming constructs used by many senders. * xref:schedulers.adoc#_inline_scheduler[`inline_scheduler`] - https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/schedulers/inline_scheduler.hpp[`#include `] * `inplace_stop_source` - https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/schedulers/stop_token.hpp[`#include `] * `inplace_stop_token`- https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/schedulers/stop_token.hpp[`#include `] +* xref:sender_adaptors.adoc#_into_variant[`into_variant`] - https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/into_variant.hpp[`#include `] * xref:sender_factories.adoc#_just[`just`] - https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/just.hpp[`#include `] * xref:sender_factories.adoc#_just_error[`just_error`] - https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/just.hpp[`#include `] * xref:sender_factories.adoc#_just_error_result_of[`just_error_result_of`] - https://github.com/intel/cpp-baremetal-senders-and-receivers/blob/main/include/async/just_result_of.hpp[`#include `] diff --git a/include/async/into_variant.hpp b/include/async/into_variant.hpp new file mode 100644 index 0000000..b641fc2 --- /dev/null +++ b/include/async/into_variant.hpp @@ -0,0 +1,130 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace async { +namespace _into_variant { +template +struct receiver { + using is_receiver = void; + using sender_t = S; + + [[nodiscard]] constexpr auto + query(async::get_env_t) const -> forwarding_env> { + return forward_env_of(r); + } + + template auto set_value(Args &&...args) && -> void { + debug_signal>( + get_env(r)); + std::move(r).set_value( + V{stdx::make_tuple(std::forward(args)...)}); + } + + template auto set_error(Args &&...args) && -> void { + debug_signal>( + get_env(r)); + std::move(r).set_error(std::forward(args)...); + } + auto set_stopped() && -> void { + debug_signal>( + get_env(r)); + std::move(r).set_stopped(); + } + + [[no_unique_address]] Rcvr r; +}; + +namespace detail { +template using discard_signatures = completion_signatures<>; + +template +using decayed_tuple = stdx::tuple...>; + +template typename V> +using variant_t = value_types_of_t; +} // namespace detail + +template typename V> +struct sender { + using is_sender = void; + + template + [[nodiscard]] constexpr auto connect(R &&r) && { + check_connect(); + using env_t = env_of_t>; + using variant_t = detail::variant_t; + return async::connect( + std::move(s), receiver>{ + std::forward(r)}); + } + + template + requires multishot_sender + [[nodiscard]] constexpr auto connect(R &&r) const & { + check_connect(); + using env_t = env_of_t>; + using variant_t = detail::variant_t; + return async::connect( + s, receiver>{ + std::forward(r)}); + } + + template + [[nodiscard]] constexpr static auto get_completion_signatures(Env const &) { + using variant_t = detail::variant_t; + return transform_completion_signatures_of< + S, Env, completion_signatures, + detail::discard_signatures>{}; + } + + [[no_unique_address]] S s; +}; + +template typename V> +struct pipeable { + private: + template Self> + friend constexpr auto operator|(S &&s, Self &&) -> async::sender auto { + return sender, V>{std::forward(s)}; + } +}; +} // namespace _into_variant + +template typename V = std::variant> +[[nodiscard]] constexpr auto into_variant() { + return _compose::adaptor<_into_variant::pipeable>{}; +} + +template typename V = std::variant, sender S> +[[nodiscard]] constexpr auto into_variant(S &&s) -> sender auto { + return std::forward(s) | into_variant(); +} + +struct into_variant_t; + +template +struct debug::context_for<_into_variant::receiver> { + using tag = into_variant_t; + constexpr static auto name = Name; + using type = _into_variant::receiver; + using children = stdx::type_list>>; +}; +} // namespace async diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 57ef80a..ecbe94e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,6 +29,7 @@ add_tests( freestanding_sync_wait hosted_sync_wait incite_on + into_variant just just_error just_error_result_of diff --git a/test/into_variant.cpp b/test/into_variant.cpp new file mode 100644 index 0000000..d0c9a41 --- /dev/null +++ b/test/into_variant.cpp @@ -0,0 +1,222 @@ +#include "detail/common.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +TEST_CASE("into_variant advertises what it sends", "[into_variant]") { + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just(3.14f); }); + [[maybe_unused]] auto iv = async::into_variant(s); + static_assert( + async::sender_of, + stdx::tuple>)>); + static_assert(not async::sender_of); +} + +TEST_CASE("into_variant advertises errors", "[into_variant]") { + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just_error(3.14f); }); + [[maybe_unused]] auto iv = async::into_variant(s); + static_assert( + async::sender_of>)>); + static_assert(async::sender_of); +} + +TEST_CASE("basic operation", "[into_variant]") { + int value{}; + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just(3.14f); }); + auto iv = async::into_variant(s); + auto op = async::connect( + iv, receiver{[&](auto i) { + std::visit(stdx::overload{[&](stdx::tuple) { value = 1; }, + [&](stdx::tuple) { value = 2; }}, + i); + }}); + async::start(op); + CHECK(value == 2); +} + +TEST_CASE("sending multiple values", "[into_variant]") { + int value{}; + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just(3.14f, 17); }); + auto iv = async::into_variant(s); + auto op = async::connect( + iv, receiver{[&](auto i) { + std::visit( + stdx::overload{[&](stdx::tuple) { value = 1; }, + [&](stdx::tuple) { value = 2; }}, + i); + }}); + async::start(op); + CHECK(value == 2); +} + +TEST_CASE("sending void values", "[into_variant]") { + int value{}; + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, [] { return async::just(); }); + auto iv = async::into_variant(s); + auto op = async::connect( + iv, receiver{[&](auto i) { + std::visit(stdx::overload{[&](stdx::tuple) { value = 1; }, + [&](stdx::tuple<>) { value = 2; }}, + i); + }}); + async::start(op); + CHECK(value == 2); +} + +TEST_CASE("sending move-only values", "[into_variant]") { + int value{}; + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just(move_only{42}); }); + auto iv = async::into_variant(std::move(s)); + auto op = async::connect( + std::move(iv), receiver{[&](auto i) { + std::visit( + stdx::overload{[&](stdx::tuple) { value = 1; }, + [&](stdx::tuple>) { value = 2; }}, + std::move(i)); + }}); + async::start(op); + CHECK(value == 2); +} + +TEST_CASE("into_variant is pipeable", "[into_variant]") { + int value{}; + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just(3.14f); }); + auto iv = s | async::into_variant(); + auto op = async::connect( + iv, receiver{[&](auto i) { + std::visit(stdx::overload{[&](stdx::tuple) { value = 1; }, + [&](stdx::tuple) { value = 2; }}, + i); + }}); + async::start(op); + CHECK(value == 2); +} + +TEST_CASE("into_variant is adaptor-pipeable", "[into_variant]") { + int value{}; + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just(3.14f); }); + + auto iv = async::into_variant() | async::then([](auto i) { return i; }); + auto op = async::connect( + s | iv, receiver{[&](auto i) { + std::visit(stdx::overload{[&](stdx::tuple) { value = 1; }, + [&](stdx::tuple) { value = 2; }}, + i); + }}); + async::start(op); + CHECK(value == 2); +} + +namespace { +template +using alt_variant = std::variant; +} + +TEST_CASE("alternative variant", "[into_variant]") { + int value{}; + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just(3.14f); }); + auto iv = async::into_variant<"alt", alt_variant>(s); + auto op = async::connect( + iv, receiver{[&](auto i) { + CHECK(std::same_as, + stdx::tuple>>); + value = 42; + }}); + async::start(op); + CHECK(value == 42); +} + +namespace { +std::vector debug_events{}; + +struct debug_handler { + template + constexpr auto signal(auto &&...) { + using namespace stdx::literals; + if constexpr (std::is_same_v, + async::into_variant_t>) { + static_assert(not boost::mp11::mp_empty< + async::debug::children_of>::value); + debug_events.push_back( + fmt::format("{} {} {}", C, async::debug::name_of, S)); + } + } +}; +} // namespace + +template <> inline auto async::injected_debug_handler<> = debug_handler{}; + +TEST_CASE("into_variant can be debugged with a string", "[into_variant]") { + using namespace std::string_literals; + debug_events.clear(); + + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just(3.14f); }) | + async::into_variant(); + auto op = async::connect( + s, with_env{universal_receiver{}, + async::prop{async::get_debug_interface_t{}, + async::debug::named_interface<"op">{}}}); + + async::start(op); + CHECK(debug_events == std::vector{"op into_variant set_value"s}); +} + +TEST_CASE("into_variant can be named and debugged with a string", + "[into_variant]") { + using namespace std::string_literals; + debug_events.clear(); + + auto s = async::make_variant_sender( + false, [] { return async::just(42); }, + [] { return async::just(3.14f); }) | + async::into_variant<"iv_name">(); + auto op = async::connect( + s, with_env{universal_receiver{}, + async::prop{async::get_debug_interface_t{}, + async::debug::named_interface<"op">{}}}); + + async::start(op); + CHECK(debug_events == std::vector{"op iv_name set_value"s}); +}