按给定条件拆分给定的std::variant类型

20

如何购买给定的变体类型

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

声明两种变量类型

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

如果V1包含来自V的所有算术类型,而V2包含V中所有非算术类型,那么这个句子的意思是什么?

V可以是模板类的参数,例如:

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

一般情况下,标准可以像这样使用一个constexpr变量:

template <class T>
constexpr bool filter;
3个回答

14

使用Boost.Mp11,这只需要一行代码(一如既往):

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

您也可以使用:

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

使这两个更对称。


或者,

using P = mp_partition<V, std::is_arithmetic>;
using V1 = mp_first<P>;
using V2 = mp_second<P>;

这个 mp_filter 基于什么思路? - Alexey Starinsky
@AlexeyStarinsky 我不明白这个问题 - 你指的是什么想法? - Barry
我不明白为什么你不理解 :) 我的意思是,它使用什么技术来转换 std::variant - Alexey Starinsky
3
@AlexeyStarinsky 阅读文档,它还链接到 Peter 写的一些文章,非常信息丰富。 - Barry
4
在我看来,这是最好的元编程库。我在这里有很多答案都以“使用Boost.Mp11,这可以只用一行短代码”开头。 - Barry
1
@Barry 我现在正在阅读文档,它看起来比boost.MPL好多了。 - Maxim Egorushkin

6

如果由于某些原因您不想使用Barry的简短和合理的答案,那么这里有一个既不是这样的答案(感谢@xskxzr删除了尴尬的“bootstrap”专业化,并感谢@max66提醒我注意空变量的情况):

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

在 Wandbox 上实时查看


也许你可以直接在 std::variant 中解包 Types...,就像这样 - xskxzr
抱歉,据我所知,一个空的 std::variant 是不合法的。 - max66
@max66 看起来只有实例化 std::variant<> 是不合法的,所以我没问题了。不过我会将 V1V2 调整为回退到 std::variant<std::monostate> - Quentin
啊...只有在实例化的时候才会形成不良形式...好的,对我来说似乎很合理。 - max66

2

编辑 根据cppreference,一个空变量(std::variant<>)是不合法的,应该使用std::variant<std::monostate>代替,因此我修改了答案(为空元组添加了tuple2variant()特化),以支持当V1V2类型列表为空的情况。


这有点decltype()的迷幻,但是...如果您声明一对帮助过滤器函数如下:

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

并创建一个元组转变量函数(对于空元组进行特殊化处理,以避免空的std::variant

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

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

你的类只需简单地变成

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

如果您想要更通用的内容(如果您想将std::arithmetic作为模板参数传递),您可以修改filterArithm()函数,通过传递一个模板-模板过滤器参数F(重命名为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 ();

TheAnswer类变成了

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

并且TA声明还需要使用 std::is_arithmetic

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

以下是一个完整的编译示例,其中参数使用std::is_arithmetic,且V2为空。
#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> );
 }

@xskxzr - 抱歉,我不理解你的反对意见。据我所知,在std::variant中,void是被禁止作为类型的。 - max66

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接