Dividir un tipo de variante std :: dado por un criterio dado

20

Cómo por un tipo de variante dado

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

declarar dos tipos de variantes

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

donde V1incluye todos los tipos aritméticos de Ve V2incluye todos los tipos no aritméticos de V?

V puede ser un parámetro de una clase de plantilla, por ejemplo:

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

en general los criterios pueden ser una constexprvariable como esta:

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

Respuestas:

6

Si por alguna razón no desea utilizar la respuesta breve y razonable de Barry, aquí hay una que no es ninguna de las dos (gracias @ xskxzr por eliminar la incómoda especialización "bootstrap" y @ max66 por advertirme sobre el caso de la esquina de la variante vacía) :

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

Véalo en vivo en Wandbox

Quentin
fuente
Tal vez se puede descomprimir Types...el interior std::variantdirectamente, como esta ?
xskxzr
Lo siento, pero ... hasta donde sé, un vacío std::variantestá mal formado.
max66
@ max66 Aparentemente, solo la creación de instancias std::variant<> está mal formada, así que estoy claro. Voy a ajustar de modo que V1y V2caer de nuevo a std::variant<std::monostate>pesar.
Quentin
Ah ... mal formado solo si se instancia ... OK; me parece razonable
max66
14

Con Boost.Mp11 , este es un breve resumen (como siempre):

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

También puedes usar:

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

para hacer los dos más simétricos.


Alternativamente,

using P = mp_partition<V, std::is_arithmetic>;
using V1 = mp_first<P>;
using V2 = mp_second<P>;
Barry
fuente
¿En qué ideas se mp_filterbasa esto ?
Alexey Starinsky
@AlexeyStarinsky No entiendo la pregunta: ¿qué quieres decir, qué ideas?
Barry
3
@AlexeyStarinsky Lea la documentación, también se vincula a algunas publicaciones que Peter escribió, es bastante informativo.
Barry
44
@MaximEgorushkin Es la mejor biblioteca de metaprogramación de la OMI. Tengo muchas respuestas aquí que comienzan con "Con Boost.Mp11, esta es una frase breve"
Barry
1
@Barry Estoy leyendo los documentos en este momento y se ve mucho mejor que boost.MPL.
Maxim Egorushkin
2

EDITAR Dado que una variante vacía ( std::variant<>) está mal formada (según cppreference ) y que debería usarse en su std::variant<std::monostate>lugar, he modificado la respuesta (agregué una tuple2variant()especialización para tuplas vacías) para admitir el caso cuando la lista de tipos para V1o V2está vacía.


Es un pequeño decltype()delirio pero ... si declaras un par de funciones de filtro auxiliar de la siguiente manera

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

y una función tupla a variante (con una especialización para tuplas vacías, para evitar un vacío std::variant)

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

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

tu clase simplemente (?) se convierte

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 desea algo más genérico (si desea pasar std::arithmeticcomo parámetro de plantilla), puede modificar la filterArithm()función pasando un parámetro de filtro de plantilla-plantilla F(renombrado 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 TheAnswerclase se convierte

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

y la TAdeclaración toma tambiénstd::is_arithmetic

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

El siguiente es un ejemplo completo de compilación con un std::is_arithmeticparámetro y un V2caso vacío

#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
fuente
Su solución no funciona void.
xskxzr
@xskxzr - Lo siento pero no entiendo tu objeción. void, que yo sepa, está prohibido como tipo en a std::variant.
max66
1
Mi mal, no me di cuenta que std::variant<void>está mal formado, pero parece que std::variant<>está bien si su definición no se instancia .
xskxzr