Разделить данный тип std::variant по заданным критериям

Как по заданному типу варианта

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

объявить два типа вариантов

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

где V1 включает все арифметические типы из V, а V2 включает все неарифметические типы из V?

V может быть параметром класса шаблона, например:

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

в общем случае критерии могут быть переменной constexpr, например:

template <class T>
constexpr bool filter;

person Alexey Starinsky    schedule 04.03.2020    source источник


Ответы (3)


Если по какой-либо причине вы не хотите использовать краткий и разумный ответ Барри, вот тот, который не является ни тем, ни другим (спасибо @xskxzr за удаление неудобной специализации "bootstrap" и @max66 за предупреждение меня о пустом варианте углового случая):

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>;

Посмотрите на Wandbox

person Quentin    schedule 04.03.2020
comment
Может быть, вы можете распаковать Types... внутри std::variant напрямую, например, этим? - person xskxzr; 04.03.2020
comment
Извините, но... насколько я знаю, пустой std::variant имеет неправильный формат. - person max66; 04.03.2020
comment
@max66 По-видимому, только создание экземпляра std::variant<> неправильно сформировано, так что я в ясности. Я изменю его так, чтобы V1 и V2 возвращались к std::variant<std::monostate>. - person Quentin; 05.03.2020
comment
Ах... плохо сформированы, только если созданы экземпляры... ОК; мне кажется разумным. - person max66; 05.03.2020

С Boost.Mp11, это короткий однострочный (как всегда):

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

Вы также можете использовать:

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

чтобы сделать два более симметричными.


Альтернативно,

using P = mp_partition<V, std::is_arithmetic>;
using V1 = mp_first<P>;
using V2 = mp_second<P>;
person Barry    schedule 04.03.2020
comment
На каких идеях основано это mp_filter? - person Alexey Starinsky; 04.03.2020
comment
@AlexeyStarinsky Я не понимаю вопроса - что вы имеете в виду, какие идеи? - person Barry; 04.03.2020
comment
Я не понимаю, почему вы не понимаете :) Я имею в виду, какую технику он использует для преобразования std::variant? - person Alexey Starinsky; 04.03.2020
comment
@AlexeyStarinsky Прочтите документацию, там также есть ссылки на некоторые посты, написанные Петром, это довольно информативно. - person Barry; 04.03.2020
comment
@MaximEgorushkin Это лучшая библиотека метапрограммирования imo. У меня здесь много ответов, которые начинаются с «With Boost.Mp11», это короткая однострочная фраза. - person Barry; 04.03.2020
comment
@Barry Я сейчас читаю документы, и они выглядят намного лучше, чем boost.MPL. - person Maxim Egorushkin; 04.03.2020

EDIT Учитывая, что пустой вариант (std::variant<>) сформирован неправильно (согласно cppreference), и вместо этого следует использовать std::variant<std::monostate>, я изменил ответ (добавил специализацию tuple2variant() для пустого кортежа), чтобы поддерживать случай, когда список типов для V1 или V2 пуст.


Это немного decltype() бред, но... если вы объявите пару функций вспомогательного фильтра следующим образом

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 ();

и функция кортежа в вариант (со специализацией для пустых кортежей, чтобы избежать пустого std::variant)

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

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

ваш класс просто (?) стал

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>()... ))>()));
 };

Если вы хотите что-то более общее (если вы хотите передать std::arithmetic в качестве параметра шаблона), вы можете изменить функцию filterArithm(), передав параметр фильтра шаблона-шаблона F (переименованный 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 ();

Класс TheAnswer становится

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>()... ))>()));
 };

и объявление TA принимает также std::is_arithmetic

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

Ниже приведен полный пример компиляции с параметром std::is_arithmetic и пустым регистром V2.

#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> );
 }
person max66    schedule 04.03.2020
comment
@xskxzr - Извините, но я не понимаю вашего возражения. void, насколько я знаю, запрещен как тип в std::variant. - person max66; 04.03.2020
comment
Плохо, я не знал, что std::variant<void> имеет неправильный формат, но кажется std::variant<> в порядке, если его определение не создано. - person xskxzr; 05.03.2020