From c104911240de93677dbd428617fe052b8efbe1ac Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Mon, 1 Apr 2024 19:52:51 -0600 Subject: [PATCH] New neo::visit --- src/neo/variant.hpp | 4 +- src/neo/variant.test.cpp | 5 +- src/neo/visit.hpp | 100 +++++++++++++++++++++++++++++++++++++++ src/neo/visit.test.cpp | 51 ++++++++++++++++++++ 4 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 src/neo/visit.hpp create mode 100644 src/neo/visit.test.cpp diff --git a/src/neo/variant.hpp b/src/neo/variant.hpp index e5453b7..5efcd1a 100644 --- a/src/neo/variant.hpp +++ b/src/neo/variant.hpp @@ -36,12 +36,12 @@ class variant : _variant_detail::variant_operators { // Extracting the first type once and reusing it is actually measurably more compile-time // efficient than calling `pack_at<0, ...>` multiple times. - using _first_type = nonvoid_t>; + using _first_type = nonvoid_t...>>; public: // Yield the Nth alternative type for the variant template - using nth_type = meta::pack_at; + using nth_type = meta::pack_at...>; /** * @brief Default constructor for the variant type. Default-constructs to the first alternative. diff --git a/src/neo/variant.test.cpp b/src/neo/variant.test.cpp index 121ea06..4fb0bad 100644 --- a/src/neo/variant.test.cpp +++ b/src/neo/variant.test.cpp @@ -77,9 +77,8 @@ neo::testing::cx_test_case DefaultConstruction = [](auto check) consteval { neo::testing::cx_test_case VoidDefault = [](auto check) consteval { variant v; check(v.index() == 0); - std::same_as> auto o = v.try_get<0>(); - check(o.has_value()); - (void)o.value(); + std::same_as auto o = v.try_get<0>(); + check(!!o); }; struct not_constexpr { diff --git a/src/neo/visit.hpp b/src/neo/visit.hpp new file mode 100644 index 0000000..7c51fb2 --- /dev/null +++ b/src/neo/visit.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include "./concepts.hpp" +#include "./fwd.hpp" +#include "./get.hpp" +#include "./invoke.hpp" +#include "./meta.hpp" +#include "./optional.hpp" + +namespace neo { + +template +concept visitable = requires(Var&& var) { + { var.index() } noexcept -> std::integral; + meta::len_v>; + try_get_nth<0>(NEO_FWD(var)); +}; + +namespace visit_detail { + +template >>> +struct impl; + +template +struct one_visitor { + static constexpr Return + apply(Variant&& var, + Func&& fn) noexcept(noexcept(NEO_INVOKE(NEO_FWD(fn), get_nth(NEO_FWD(var))))) { + return NEO_INVOKE(NEO_FWD(fn), get_nth(NEO_FWD(var))); + } +}; + +template +struct impl> { + // Use common_reference to find the common return type of all the visit overloads + using result_type = std::common_reference_t>...>; + + // Apply the visitor to the variant. + constexpr static result_type invoke(Var&& v, Fn&& fn) { + const auto idx = static_cast(v.index()); + // We do different things depending on the result type, just for optimization + if constexpr (void_type) { + // The result type is void, so we don't need to record the return value. Just + // invoke the appropriate overload by using pack expansion: + static_cast( + ((idx == Ns + and (static_cast(NEO_INVOKE(NEO_FWD(fn), get_nth(NEO_FWD(v)))), true)) + or ...)); + } else if constexpr (move_constructible) { + // The result type is move-constructible, so do the invoke by emplacing the + // return value in an optional and then returning that value + optional r; + static_cast( + ((idx == Ns and (r.emplace(NEO_INVOKE(NEO_FWD(fn), get_nth(NEO_FWD(v)))), true)) + or ...)); + result_type ret = *NEO_MOVE(r); + return ret; + } else { + // The result type is not move constructible, so we can't do a pack expanion trick. + // Instead, create a unique visit trampoline function in an array of function pointers + // and invoke the correct one based on the variant's current index: + constexpr result_type (*functions[])(Var&& variant, Fn&& fn) + = {one_visitor::apply...}; + auto chosen = functions[idx]; + return chosen(NEO_FWD(v), NEO_FWD(fn)); + } + } +}; + +} // namespace visit_detail + +/** + * @brief Yield the result type of applying the given visitor to the given variant-like operand + * + * @tparam Var A variant-like type + * @tparam Fn A visitor function for the variant. + * + * If the visitor is not valid for the variant, no type is defined + */ +template +using visit_t = typename visit_detail::impl::result_type; + +/** + * @brief Match a visitor invocable to a variant to be visited + * + * @tparam Fn An invocable object, the prospective visitor + * @tparam Var The variant to be visited + */ +template +concept visitor_of = visitable and requires { typename visit_t; }; + +template Fn> +constexpr visit_t visit(Var&& var, Fn&& fn) { + using impl = visit_detail::impl; + return impl::invoke(NEO_FWD(var), NEO_FWD(fn)); +} + +} // namespace neo diff --git a/src/neo/visit.test.cpp b/src/neo/visit.test.cpp new file mode 100644 index 0000000..f2a3205 --- /dev/null +++ b/src/neo/visit.test.cpp @@ -0,0 +1,51 @@ +#include "./visit.hpp" +#include "neo/testing.hpp" +#include "neo/variant.hpp" + +#include + +#include +#include +#include + +#include + +TEST_CASE("Basic visit") { + std::variant s2; + auto r = neo::visit(s2, [](auto) { return 12; }); + CHECK(r == 12); +} + +neo::testing::cx_test_case NeoVariantVisit = [](auto check) consteval { + neo::variant var; + auto res = neo::visit(var, [](auto) { return 42; }); + check(res == 42); +}; + +neo::testing::cx_test_case VisitVariantWithVoid = [](auto check) consteval { + neo::variant var; + auto r = neo::visit(var, [](auto) { return 1729; }); + check(r == 1729); +}; + +struct base {}; + +struct derived : base {}; + +neo::testing::cx_test_case CommonReference = [](auto) consteval { + neo::variant var; + std::same_as decltype(auto) b + = neo::visit(var, [](auto&& x) -> decltype(auto) { return x; }); + (void)b; +}; + +struct immobile { + immobile() = default; + immobile(const immobile&) = delete; +}; + +neo::testing::cx_test_case VisitWithImmobileresult = [](auto) consteval { + neo::variant var; + immobile im = neo::visit(var, [](auto) -> immobile { return {}; }); + (void)im; +};