/* ***************************************************************************** * kblib is a general utility library for C++14 and C++17, intended to provide * performant high-level abstractions and more expressive ways to do simple * things. * * Copyright (c) 2021 killerbee * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ****************************************************************************/ /** * @file * @brief Provides utilities for working with std::variant more expressively and * more efficiently. * * @author killerbee * @date 2019-2021 * @copyright GNU General Public Licence v3.0 */ #ifndef KBLIB_VARIANT_H #define KBLIB_VARIANT_H #include "convert.h" #include "logic.h" #include "tdecl.h" #include #include #if KBLIB_USE_CXX17 # include #endif namespace KBLIB_NS { #if KBLIB_USE_CXX17 template constexpr inline bool is_variant_like_v = false; template constexpr inline bool is_variant_like_v>> = true; template struct is_variant_like : std::bool_constant> {}; /** * @brief Lexically converts the value of v (no matter its type) to type To. * * @deprecated Use lexical_coerce instead, as it more clearly expresses intent. * * @param v A variant to coerce. * @return To The type to coerce to. */ template KBLIB_NODISCARD [[deprecated("Use new lexical_coerce instead.")]] auto coerce( const std::variant& v) -> To { return std::visit([](const auto& t) -> To { return lexical_cast(t); }, v); } /** * @brief static_casts the value of v (no matter its type) to type To. * * @param v A variant to coerce. * @return To The type to coerce to. */ template KBLIB_NODISCARD auto static_coerce(const std::variant& v) -> To { return std::visit([](const auto& t) -> To { return static_cast(t); }, v); } /** * @brief Lexically converts the value of v (no matter its type) to type To. * * @param v A variant to coerce. * @return To The type to coerce to. */ template KBLIB_NODISCARD auto lexical_coerce(const std::variant& v) -> To { return std::visit([](const auto& t) { return lexical_cast(t); }, v); } /** * @brief Helper class for std::visiting a std::variant. * * When constructed from a set of lambdas or functors, corrals all of them into * a single overload set, suitable for std::visit. */ template struct visitor : Ts... { using Ts::operator()...; }; template visitor(Ts...) -> visitor; namespace detail { /** * @brief Given a std::variant T, provides the member type which is a tuple * of the same types. */ template struct tuple_type { /** * @brief Non-variant inputs produce the empty tuple. */ using type = std::tuple<>; }; /** * @brief Given a std::variant T, provides the member type which is a tuple * of the same types. */ template struct tuple_type> { /** * @brief A tuple of the same types as T is a variant of. */ using type = std::tuple; }; /** * Equivalent to typename tuple_type::type */ template using tuple_type_t = typename tuple_type::type; /** * @brief Generates an array of function pointers which will unwrap the * variant and pass the index to the function. */ template constexpr auto indexed_visitor_impl(std::index_sequence) -> auto { return std::array{+[](Variant&& variant, F&& f) { return std::forward(f)( kblib::constant{}, std::get(std::forward(variant))); }...}; } } // namespace detail inline namespace literals { template KBLIB_NODISCARD constexpr auto operator""_vi() { constexpr char arr[] = {Cs...}; return std::in_place_index_t(arr)>{}; } } // namespace literals /** * @brief Visit a variant, but pass the index (as an integral_constant) to the * visitor. This allows for a visitor of a variant with duplicated types to * maintain index information. * * @param variant The variant to visit. * @param fs Any number of functors, which taken together as an overload set can * be unambiguously called with (I, A), * for I = kblib::constant * and A = std::get(variant). * Note that kblib::constant implicitly converts to std::integral_constant. */ template constexpr auto visit_indexed(Variant&& variant, Fs&&... fs) -> decltype(auto) { if (variant.valueless_by_exception()) { throw std::bad_variant_access(); } using visitor_t = decltype(visitor{std::forward(fs)...}); return detail::indexed_visitor_impl( std::make_index_sequence< std::variant_size_v>>())[variant.index()]( std::forward(variant), visitor{std::forward(fs)...}); } /** * @brief Promotes an input variant to a super-variant. That is, one which * provides at least the same set of types. * @param v The variant to promote. * @return To A super-variant with the same value as v. */ template KBLIB_NODISCARD constexpr auto variant_cast(From&& v) -> To { static_assert(contains_types_v>, detail::tuple_type_t>>, "To must include all types in From"); return visit_indexed(std::forward(v), [](auto constant, auto&& x) { return To(std::in_place_type< std::variant_alternative_t>>, std::forward(x)); }); // return std::visit([](auto&& x) { return std::forward(x); }, // std::forward(v)); } /** * @brief Wraps std::visit to provide an interface taking one variant and any * number of functors providing an overload set. * * Also moves the variant to the left side of the operation, improving * readability. * * @param v The variant to visit over. * @param fs Any number of functors, which taken together as an overload set can * be unambiguously called with any type in V. */ template KBLIB_NODISCARD constexpr auto visit(V&& v, F&& f, Fs&&... fs) -> decltype(auto) { return std::visit(visitor{std::forward(f), std::forward(fs)...}, std::forward(v)); } namespace detail { template constexpr bool invocable_with_all_v = (ignore_t, std::true_type>::value and ...); template constexpr bool v_invocable_with_all_v = false; template constexpr bool v_invocable_with_all_v< F, std::variant> = invocable_with_all_v; template constexpr bool v_invocable_with_all_v< F, const std::variant> = invocable_with_all_v; template constexpr bool v_invocable_with_all_v< F, std::variant&> = invocable_with_all_v; template constexpr bool v_invocable_with_all_v< F, const std::variant&> = invocable_with_all_v; template constexpr bool v_invocable_with_all_v< F, std::variant&&> = invocable_with_all_v; template constexpr bool v_invocable_with_all_v< F, const std::variant&&> = invocable_with_all_v; template [[gnu::always_inline]] constexpr decltype(auto) visit_impl( V&& v, F&& f, std::index_sequence) { static_assert(I < std::variant_size_v>); if (auto* p = std::get_if(&v)) { return std::forward(f)( static_cast(std::forward(v)))>(*p)); } else if constexpr (sizeof...(Is) > 0) { return visit_impl(std::forward(v), std::forward(f), std::index_sequence{}); } else { throw std::bad_variant_access(); } } template [[gnu::always_inline]] constexpr void visit_nop_impl( V&& v, F&& f, std::index_sequence) { static_assert(I < std::variant_size_v>); if (auto* p = std::get_if(&v)) { std::forward(f)( static_cast(std::forward(v)))>(*p)); return; } else if constexpr (sizeof...(Is) > 0) { return visit_nop_impl(std::forward(v), std::forward(f), std::index_sequence{}); } else { // valueless_by_exception case return; } } } // namespace detail template [[gnu::always_inline]] constexpr auto visit2(V&& v, F&& f, Fs&&... fs) -> decltype(auto) { auto visitor_obj = visitor{std::forward(f), std::forward(fs)...}; static_assert(detail::v_invocable_with_all_v, "Some variant types not accepted by any visitors."); return detail::visit_impl( std::forward(v), std::move(visitor_obj), std::make_index_sequence>>{}); } template [[gnu::always_inline]] constexpr auto visit2_nop(V&& v, F&& f, Fs&&... fs) -> void { auto visitor_obj = visitor{std::forward(f), std::forward(fs)...}; static_assert(detail::v_invocable_with_all_v, "Some variant types not accepted by any visitors."); return detail::visit_nop_impl( std::forward(v), visitor_obj, std::make_index_sequence>>{}); } /** * @brief Two-step visiting interface. Takes a variant, and returns an object * which can be called with any number of callable arguments, builds an overload * set from them, and visits the variant. * * * * @note The returned callable object contains a reference to v, so care must be * taken to avoid dangling. However, if v is long-lived, the returned object * may be stored and used to visit the same variant multiple times. * * @param v A variant to visit. * @return auto A callable object which takes callable arguments and visits the * visitor. */ template KBLIB_NODISCARD constexpr auto visit(V& v) -> auto { return [&v](auto... fs) -> decltype(auto) { return kblib::visit(v, std::forward(fs)...); }; } #endif // KBLIB_USE_CXX17 } // namespace KBLIB_NS #endif // KBLIB_VARIANT_H