Teilen Sie einen bestimmten std :: varianten-Typ nach einem bestimmten Kriterium

20

Wie durch einen bestimmten Variantentyp

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

deklarieren Sie zwei Variantentypen

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

Woher V1gehören alle arithmetischen Typen von Vund V2alle nicht-arithmetischen Typen von V?

V kann ein Parameter einer Vorlagenklasse sein, zum Beispiel:

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

Im Allgemeinen können die Kriterien eine constexprVariable wie diese sein:

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

Antworten:

6

Wenn Sie aus irgendeinem Grund die kurze und vernünftige Antwort von Barry nicht verwenden möchten, ist hier eine, die keine ist (danke @ xskxzr für das Entfernen der umständlichen "Bootstrap" -Spezialisierung und @ max66 für die Warnung vor dem leeren Fall einer Variante). ::

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

Sehen Sie es live auf Wandbox

QUentin
quelle
Vielleicht können Sie entpacken Types...innen std::variantdirekt, wie das ?
xskxzr
Sorry, aber ... soweit ich weiß, ist ein leerer std::variantschlecht geformt.
max66
@ max66 Anscheinend ist nur das Instanziieren std::variant<> schlecht geformt, also bin ich im klaren. Ich werde es so optimieren V1und darauf V2zurückgreifen std::variant<std::monostate>.
Quentin
Ah ... nur schlecht geformt, wenn instanziiert ... OK; scheint mir vernünftig.
max66
14

Bei Boost.Mp11 ist dies (wie immer) ein kurzer Einzeiler :

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

Sie können auch verwenden:

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

um die beiden symmetrischer zu machen.


Alternative,

using P = mp_partition<V, std::is_arithmetic>;
using V1 = mp_first<P>;
using V2 = mp_second<P>;
Barry
quelle
Auf welchen Ideen mp_filterbasiert das?
Alexey Starinsky
@AlexeyStarinsky Ich verstehe die Frage nicht - was meinst du, welche Ideen?
Barry
3
@AlexeyStarinsky Lesen Sie die Dokumentation, sie enthält auch Links zu einigen Posts, die Peter geschrieben hat. Sie ist sehr informativ.
Barry
4
@ MaximEgorushkin Es ist die beste Metaprogrammierbibliothek imo. Ich habe hier viele Antworten, die mit "Mit Boost.Mp11 ist dies ein kurzer Einzeiler" beginnen
Barry
1
@Barry Ich lese gerade die Dokumente und es sieht viel besser aus als boost.MPL.
Maxim Egorushkin
2

BEARBEITEN Da eine leere Variante ( std::variant<>) schlecht geformt ist (gemäß cppreference ) und std::variant<std::monostate>stattdessen verwendet werden sollte, habe ich die Antwort geändert (eine tuple2variant()Spezialisierung für leeres Tupel hinzugefügt ), um den Fall zu unterstützen, wenn die Liste der Typen für V1oder V2leer ist.


Es ist ein kleines decltype()Delirium, aber ... wenn Sie ein Hilfsfilter-Funktionspaar wie folgt deklarieren

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

und eine Tupel-Varianten-Funktion (mit einer Spezialisierung für leere Tupel, um ein leeres zu vermeiden std::variant)

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

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

Ihre Klasse wird einfach (?)

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

Wenn Sie etwas allgemeineres wünschen (wenn Sie std::arithmeticals Vorlagenparameter übergeben möchten ), können Sie die filterArithm()Funktion ändern, die einen Vorlagenparameter-Filterparameter übergibt F(umbenannt 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 ();

Die TheAnswerKlasse wird

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

und die TAErklärung nehmen auchstd::is_arithmetic

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

Das Folgende ist ein vollständiges Kompilierungsbeispiel mit std::is_arithmeticals Parameter und einem V2leeren Fall

#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
quelle
Ihre Lösung funktioniert nicht für void.
xskxzr
@xskxzr - Entschuldigung, aber ich verstehe Ihren Einwand nicht. void, soweit ich weiß, ist als Eingabe in a verboten std::variant.
max66
1
Mein schlechtes, ich wusste nicht, dass std::variant<void>es schlecht geformt ist, aber es scheint std::variant<>in Ordnung zu sein, wenn seine Definition nicht instanziiert wird .
xskxzr