Split a given std::variant type by a given criteria
Asked Answered
J

3

20

How to by a given variant type

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

declare two variant types

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

where V1 includes all the arithmetic types from V and V2 includes all non-arithmetic types from V?

V can be a parameter of a template class, for example:

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

in general the criteria can be a constexpr variable like this:

template <class T>
constexpr bool filter;
Jasun answered 4/3, 2020 at 14:38 Comment(0)
C
6

If for whatever reason you don't want to use Barry's short and reasonable answer, here is one that is neither (thanks @xskxzr for removing the awkward "bootstrap" specialization, and to @max66 for warning me against the empty variant corner case):

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

See it live on Wandbox

Changeup answered 4/3, 2020 at 14:59 Comment(4)
Maybe you can unpack Types... inside std::variant directly, like this?Gladisgladney
Sorry, but... as far I know, a empty std::variant is ill-formed.Chippy
@Chippy Apparently only instantiating std::variant<> is ill-formed, so I'm in the clear. I will tweak it so that V1 and V2 fall back to std::variant<std::monostate> though.Changeup
Ah... ill-formed only if instantiated... OK; seems reasonable to me.Chippy
E
14

With Boost.Mp11, this is a short one-liner (as always):

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

You can also use:

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

to make the two more symmetric.


Alternatively,

using P = mp_partition<V, std::is_arithmetic>;
using V1 = mp_first<P>;
using V2 = mp_second<P>;
Equivalent answered 4/3, 2020 at 14:48 Comment(6)
What ideas is this mp_filter based on?Jasun
@AlexeyStarinsky I don't understand the question - what do you mean, what ideas?Equivalent
I do not understand why you do not understand :) I mean what technique does it use to transform std::variant?Jasun
@AlexeyStarinsky Read the documentation, it also links to some posts that Peter wrote, it's quite informative.Equivalent
@MaximEgorushkin It's the best metaprogramming library imo. I have a lot of answers on here that start with "With Boost.Mp11, this is a short one-liner"Equivalent
@Equivalent I am reading the docs right now and it looks much better than boost.MPL.Bader
C
6

If for whatever reason you don't want to use Barry's short and reasonable answer, here is one that is neither (thanks @xskxzr for removing the awkward "bootstrap" specialization, and to @max66 for warning me against the empty variant corner case):

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

See it live on Wandbox

Changeup answered 4/3, 2020 at 14:59 Comment(4)
Maybe you can unpack Types... inside std::variant directly, like this?Gladisgladney
Sorry, but... as far I know, a empty std::variant is ill-formed.Chippy
@Chippy Apparently only instantiating std::variant<> is ill-formed, so I'm in the clear. I will tweak it so that V1 and V2 fall back to std::variant<std::monostate> though.Changeup
Ah... ill-formed only if instantiated... OK; seems reasonable to me.Chippy
C
2

EDIT Given that a empty variant (std::variant<>) is ill formed (according cppreference) and that should be used std::variant<std::monostate> instead, I've modified the answer (added a tuple2variant() specialization for empty tuple) to support the case when the list of types for V1 or V2 is empty.


It's a little decltype() delirium but... if you declare a helper filter couple of function as follows

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

and a tuple to variant function (with a specialization for empty tuples, to avoid a empty std::variant)

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

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

your class simply (?) become

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

If you want something more generic (if you want to pass std::arithmetic as a template parameter), you can modify the filterArithm() function passing a template-template filter parameter F (renamed 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 ();

The TheAnswer class become

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

and the TA declaration take also std::is_arithmetic

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

The following is a full compiling example with std::is_arithmetic as parameter and a V2 empty case

#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> );
 }
Chippy answered 4/3, 2020 at 14:59 Comment(2)
@Gladisgladney - Sorry but I don't understand your objection. void, as far I know, is forbidden as type in a std::variant.Chippy
My bad, I didn't realize std::variant<void> is ill-formed, but it seems std::variant<> is OK if its definition is not instantiated.Gladisgladney

© 2022 - 2024 — McMap. All rights reserved.