/* *****************************************************************************
* 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