Dividir um determinado tipo std :: variant por um determinado critério

20

Como por um determinado tipo de variante

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

declarar dois tipos de variantes

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

de onde V1inclui todos os tipos aritméticos de Ve V2inclui todos os tipos não-aritméticos de V?

V pode ser um parâmetro de uma classe de modelo, por exemplo:

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

em geral, os critérios podem ser uma constexprvariável como esta:

template <class T>
constexpr bool filter;
Alexey Starinsky
fonte

Respostas:

6

Se, por qualquer motivo, você não quiser usar a resposta curta e razoável de Barry, aqui está uma que não é nenhuma (obrigado @ xskxzr por remover a especialização "bootstrap" desajeitada e @ max66 por me avisar sobre o caso de canto vazio vazio) :

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

Veja ao vivo no Wandbox

Quentin
fonte
Talvez você pode descompactar Types...dentro std::variantdiretamente, como este ?
xskxzr 4/03
Desculpe, mas ... até onde eu sei, um vazio std::variantestá mal formado.
max66 4/03
@ max66 Aparentemente, apenas a instanciação std::variant<> é mal formada, então estou livre. Vou ajustá-lo para isso V1e V2voltar ao std::variant<std::monostate>pensamento.
Quentin
Ah ... mal formado apenas se instanciado ... OK; parece razoável para mim.
max66 5/03
14

Com o Boost.Mp11 , este é um one-liner curto (como sempre):

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

Você também pode usar:

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

para tornar os dois mais simétricos.


Alternativamente,

using P = mp_partition<V, std::is_arithmetic>;
using V1 = mp_first<P>;
using V2 = mp_second<P>;
Barry
fonte
Em que idéias isso se mp_filterbaseia?
Alexey Starinsky
@AlexeyStarinsky Não entendi a pergunta - como assim, que idéias?
Barry
3
@AlexeyStarinsky Leia a documentação, que também tem links para alguns posts que Peter escreveu, é bastante informativa.
Barry
4
@MaximEgorushkin É a melhor imo da biblioteca de metaprogramação. Eu tenho muitas respostas aqui que começam com "Com Boost.Mp11, este é um resumo curto"
Barry
11
@ Barry Estou lendo os documentos agora e parece muito melhor do que boost.MPL.
Maxim Egorushkin
2

EDIT Dado que uma variante vazia ( std::variant<>) está mal formada (de acordo com cppreference ) e que deve ser usada std::variant<std::monostate>, modifiquei a resposta (adicionei uma tuple2variant()especialização para tupla vazia) para dar suporte ao caso em que a lista de tipos para V1ou V2está vazia.


É um pouco de decltype()delírio, mas ... se você declarar algumas funções de filtro auxiliar da seguinte maneira

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

e uma função tupla para variante (com uma especialização para tuplas vazias, para evitar uma vazia std::variant)

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

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

sua classe simplesmente (?) se torna

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

Se você quiser algo mais genérico (se desejar passar std::arithmeticcomo parâmetro de modelo), poderá modificar a filterArithm()função que passa por um parâmetro de filtro de modelo-modelo F(renomeado 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 ();

A TheAnswerturma se torna

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

e a TAdeclaração tambémstd::is_arithmetic

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

A seguir, é apresentado um exemplo completo de compilação com std::is_arithmeticcomo parâmetro e um V2caso vazio

#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
fonte
Sua solução não funciona void.
xskxzr 4/03
@xskxzr - Desculpe, mas não entendo sua objeção. void, até onde eu sei, é proibido como digitar a std::variant.
max66 4/03
11
Meu mal, eu não percebi que std::variant<void>é mal formado, mas parece que std::variant<>está bem se sua definição não for instanciada .
xskxzr 5/03