Podziel dany typ std :: variant według podanych kryteriów

20

Jak według danego typu wariantu

using V = std::variant<bool, char, std::string, int, float, double, std::vector<int>>;

zadeklaruj dwa typy wariantów

using V1 = std::variant<bool, char, int, float, double>;
using V2 = std::variant<std::string, std::vector<int>>;

gdzie V1obejmuje wszystkie typy arytmetyczne z Vi V2obejmuje wszystkie typy arytmetyczne z V?

V może być parametrem klasy szablonów, na przykład:

template <class V>
struct TheAnswer
{
    using V1 = ?;
    using V2 = ?;
};

ogólnie kryteria mogą być constexprzmienne takie jak ta:

template <class T>
constexpr bool filter;
Aleksiej Stariński
źródło

Odpowiedzi:

6

Jeśli z jakiegokolwiek powodu nie chcesz użyć krótkiej i rozsądnej odpowiedzi Barry'ego, oto jedna z nich nie jest (dzięki @ xskxzr za usunięcie niewygodnej specjalizacji „bootstrap” i @ @ 66 za ostrzeżenie mnie przed pustą skrzynką z wariantem) :

namespace detail {
    template <class V>
    struct convert_empty_variant {
        using type = V;
    };

    template <>
    struct convert_empty_variant<std::variant<>> {
        using type = std::variant<std::monostate>;
    };

    template <class V>
    using convert_empty_variant_t = typename convert_empty_variant<V>::type;

    template <class V1, class V2, template <class> class Predicate, class V>
    struct split_variant;

    template <class V1, class V2, template <class> class Predicate>
    struct split_variant<V1, V2, Predicate, std::variant<>> {
        using matching = convert_empty_variant_t<V1>;
        using non_matching = convert_empty_variant_t<V2>;
    };

    template <class... V1s, class... V2s, template <class> class Predicate, class Head, class... Tail>
    struct split_variant<std::variant<V1s...>, std::variant<V2s...>, Predicate, std::variant<Head, Tail...>>
    : std::conditional_t<
        Predicate<Head>::value,
        split_variant<std::variant<V1s..., Head>, std::variant<V2s...>, Predicate, std::variant<Tail...>>,
        split_variant<std::variant<V1s...>, std::variant<V2s..., Head>, Predicate, std::variant<Tail...>>
    > { };
}

template <class V, template <class> class Predicate>
using split_variant = detail::split_variant<std::variant<>, std::variant<>, Predicate, V>;

Zobacz na żywo w Wandbox

Quentin
źródło
Być może można rozpakować Types...wewnątrz std::variantbezpośrednio, jak to ?
xskxzr
Przepraszam, ale ... o ile wiem, pusty std::variantjest źle ukształtowany.
max66
@ max66 Najwyraźniej tylko tworzenie instancji std::variant<> jest źle sformułowane, więc nie mam wątpliwości. Zmodyfikuję go tak, aby V1i V2wrócił do std::variant<std::monostate>tego.
Quentin
Ach ... źle sformowane tylko w przypadku wystąpienia wystąpienia ... OK; wydaje mi się rozsądne.
max66
14

W przypadku Boost.Mp11 jest to krótki linijka (jak zawsze):

using V1 = mp_filter<std::is_arithmetic, V>;
using V2 = mp_remove_if<V, std::is_arithmetic>;

Możesz także użyć:

using V1 = mp_copy_if<V, std::is_arithmetic>;

aby dwa były bardziej symetryczne.


Alternatywnie,

using P = mp_partition<V, std::is_arithmetic>;
using V1 = mp_first<P>;
using V2 = mp_second<P>;
Barry
źródło
Na jakich pomysłach się mp_filteropiera?
Alexey Starinsky
@AlexeyStarinsky Nie rozumiem pytania - co masz na myśli, jakie pomysły?
Barry
3
@AlexeyStarinsky Przeczytaj dokumentację, a także linki do niektórych postów napisanych przez Piotra, jest to dość pouczające.
Barry
4
@MaximEgorushkin To najlepsza biblioteka metaprogramowania imo. Mam tutaj wiele odpowiedzi, które zaczynają się od „With Boost.Mp11, to jest krótki linijka”
Barry
1
@ Barry Czytam teraz dokumenty i wygląda o wiele lepiej niż boost.MPL.
Maxim Egorushkin
2

EDYCJA Biorąc pod uwagę, że pusty wariant ( std::variant<>) jest źle sformułowany (zgodnie z preferencją cp ) i powinienem go użyć std::variant<std::monostate>, zmodyfikowałem odpowiedź (dodałem tuple2variant()specjalizację dla pustej krotki), aby obsługiwała przypadek, gdy lista typów dla V1lub V2jest pusta.


To trochę decltype()majaczenie, ale ... jeśli zadeklarujesz kilka filtrów pomocniczych w następujący sposób

template <bool B, typename T>
constexpr std::enable_if_t<B == std::is_arithmetic_v<T>, std::tuple<T>>
   filterArithm ();

template <bool B, typename T>
constexpr std::enable_if_t<B != std::is_arithmetic_v<T>, std::tuple<>>
   filterArithm ();

oraz funkcja krotki do wariantu (ze specjalizacją dla pustych krotek, aby uniknąć pustej std::variant)

std::variant<std::monostate> tuple2variant (std::tuple<> const &);

template <typename ... Ts>
std::variant<Ts...> tuple2variant (std::tuple<Ts...> const &);

twoja klasa po prostu (?) się

template <typename ... Ts>
struct TheAnswer<std::variant<Ts...>>
 {
   using V1 = decltype(tuple2variant(std::declval<
                 decltype(std::tuple_cat( filterArithm<true, Ts>()... ))>()));
   using V2 = decltype(tuple2variant(std::declval<
                 decltype(std::tuple_cat( filterArithm<false, Ts>()... ))>()));
 };

Jeśli chcesz mieć coś bardziej ogólnego (jeśli chcesz przekazać std::arithmeticjako parametr szablonu), możesz zmodyfikować filterArithm()funkcję, przekazując parametr filtru szablonu-szablonu F(przemianowany filterType())

template <template <typename> class F, bool B, typename T>
constexpr std::enable_if_t<B == F<T>::value, std::tuple<T>>
   filterType ();

template <template <typename> class F, bool B, typename T>
constexpr std::enable_if_t<B != F<T>::value, std::tuple<>>
   filterType ();

TheAnswerKlasa stać

template <typename, template <typename> class>
struct TheAnswer;

template <typename ... Ts, template <typename> class F>
struct TheAnswer<std::variant<Ts...>, F>
 {
   using V1 = decltype(tuple2variant(std::declval<decltype(
                 std::tuple_cat( filterType<F, true, Ts>()... ))>()));
   using V2 = decltype(tuple2variant(std::declval<decltype(
                 std::tuple_cat( filterType<F, false, Ts>()... ))>()));
 };

i TAdeklaracja równieżstd::is_arithmetic

using TA = TheAnswer<std::variant<bool, char, std::string, int, float,
                                  double, std::vector<int>>,
                     std::is_arithmetic>;

Poniżej znajduje się pełny przykład kompilacji z std::is_arithmeticparametrem as i V2pustą wielkością liter

#include <tuple>
#include <string>
#include <vector>
#include <variant>
#include <type_traits>

std::variant<std::monostate> tuple2variant (std::tuple<> const &);

template <typename ... Ts>
std::variant<Ts...> tuple2variant (std::tuple<Ts...> const &);

template <template <typename> class F, bool B, typename T>
constexpr std::enable_if_t<B == F<T>::value, std::tuple<T>>
   filterType ();

template <template <typename> class F, bool B, typename T>
constexpr std::enable_if_t<B != F<T>::value, std::tuple<>>
   filterType ();

template <typename, template <typename> class>
struct TheAnswer;

template <typename ... Ts, template <typename> class F>
struct TheAnswer<std::variant<Ts...>, F>
 {
   using V1 = decltype(tuple2variant(std::declval<decltype(
                 std::tuple_cat( filterType<F, true, Ts>()... ))>()));
   using V2 = decltype(tuple2variant(std::declval<decltype(
                 std::tuple_cat( filterType<F, false, Ts>()... ))>()));
 };

int main ()
 {
   using TA = TheAnswer<std::variant<bool, char, std::string, int, float,
                                     double, std::vector<int>>,
                        std::is_arithmetic>;
   using TB = TheAnswer<std::variant<bool, char, int, float, double>,
                        std::is_arithmetic>;

   using VA1 = std::variant<bool, char, int, float, double>;
   using VA2 = std::variant<std::string, std::vector<int>>;
   using VB1 = VA1;
   using VB2 = std::variant<std::monostate>;

   static_assert( std::is_same_v<VA1, TA::V1> );
   static_assert( std::is_same_v<VA2, TA::V2> );
   static_assert( std::is_same_v<VB1, TB::V1> );
   static_assert( std::is_same_v<VB2, TB::V2> );
 }
max66
źródło
Twoje rozwiązanie nie działa void.
xskxzr
@xskxzr - Przepraszam, ale nie rozumiem twojego sprzeciwu. void, o ile wiem, zabronione jest wpisywanie std::variant.
max66
1
Moje zło, nie zdawałem sobie sprawy, że std::variant<void>jest źle sformułowane, ale wydaje się std::variant<>być w porządku, jeśli jego definicja nie zostanie utworzona .
xskxzr