Fractionner un type std :: variant donné par un critère donné

20

Comment par un type de variante donné

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

déclarer deux types de variantes

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

d'où V1inclut tous les types arithmétiques Vet V2inclut tous les types non arithmétiques V?

V peut être un paramètre d'une classe de modèle, par exemple:

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

en général, les critères peuvent être une constexprvariable comme celle-ci:

template <class T>
constexpr bool filter;
Alexey Starinsky
la source

Réponses:

6

Si pour une raison quelconque vous ne voulez pas utiliser la réponse courte et raisonnable de Barry, voici celle qui n'est ni l'un ni l'autre (merci @ xskxzr pour avoir supprimé la spécialisation maladroite "bootstrap", et @ max66 pour m'avertir contre le cas de coin de variante vide) :

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

Voir en direct sur Wandbox

Quentin
la source
Peut-être que vous pouvez déballer l' Types...intérieur std::variantdirectement, comme ça ?
xskxzr
Désolé, mais ... pour autant que je sache, un vide std::variantest mal formé.
max66
@ max66 Apparemment, seule l' instanciation std::variant<> est mal formée, donc je suis clair. Je tweak pour que V1et V2retomber à std::variant<std::monostate>bien.
Quentin
Ah ... mal formé seulement si instancié ... OK; me semble raisonnable.
max66
14

Avec Boost.Mp11 , il s'agit d'un court one-liner (comme toujours):

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

Vous pouvez aussi utiliser:

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

pour rendre les deux plus symétriques.


Alternativement,

using P = mp_partition<V, std::is_arithmetic>;
using V1 = mp_first<P>;
using V2 = mp_second<P>;
Barry
la source
mp_filterSur quelles idées est-ce basé?
Alexey Starinsky
@AlexeyStarinsky Je ne comprends pas la question - que voulez-vous dire, quelles idées?
Barry
3
@AlexeyStarinsky Lisez la documentation, elle contient également des liens vers certains articles que Peter a écrits, c'est assez instructif.
Barry
4
@MaximEgorushkin C'est la meilleure bibliothèque de métaprogrammation imo. J'ai beaucoup de réponses ici qui commencent par "Avec Boost.Mp11, c'est un court one-liner"
Barry
1
@Barry Je lis les documents en ce moment et ça a l'air bien mieux que boost.MPL.
Maxim Egorushkin
2

EDIT Étant donné qu'une variante vide ( std::variant<>) est mal formée (selon cppreference ) et qu'elle devrait être utilisée à la std::variant<std::monostate>place, j'ai modifié la réponse (ajouté une tuple2variant()spécialisation pour le tuple vide) pour prendre en charge le cas lorsque la liste des types pour V1ou V2est vide.


C'est un petit decltype()délire mais ... si vous déclarez un couple de filtres auxiliaires de fonction comme suit

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

et un tuple à la fonction variant (avec une spécialisation pour les tuples vides, pour éviter un vide std::variant)

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

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

votre classe devient simplement (?)

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

Si vous voulez quelque chose de plus générique (si vous voulez passer std::arithmeticcomme paramètre de modèle), vous pouvez modifier la filterArithm()fonction en passant un paramètre de filtre modèle-modèle F(renommé 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 ();

La TheAnswerclasse devient

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

et la TAdéclaration prend égalementstd::is_arithmetic

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

Ce qui suit est un exemple de compilation complet avec std::is_arithmeticcomme paramètre et un V2cas vide

#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
la source
Votre solution ne fonctionne pas void.
xskxzr
@xskxzr - Désolé mais je ne comprends pas votre objection. void, pour autant que je sache, est interdit comme type dans un std::variant.
max66
1
Mon mauvais, je ne me suis pas rendu compte qu'il std::variant<void>est mal formé, mais il semble que ce std::variant<>soit OK si sa définition n'est pas instanciée .
xskxzr