【问题标题】:Can I extend variant in C++?我可以在 C++ 中扩展变体吗?
【发布时间】:2018-09-18 20:02:00
【问题描述】:

我不确定这是否可行,但说我有:

using my_variant = std::variant<Class1, Class2, Class3>;

现在,我创建了一个Class4 并希望扩展my_variant2 以包括所有my_variant 以及Class4(以一般方式,即不只是使用另一个using...)所以我可以做一些事情,比如创建一个数组std::array<my_variant2, n>

这是可以做到的吗?

【问题讨论】:

    标签: c++ templates c++17 template-meta-programming


    【解决方案1】:

    有两个步骤可以实现这一目标。第一个是识别原始 std::variant 中使用的类型列表,第二个是使用原始参数和要添加的参数构造一个新的 std::variant 类型。

    部分模板特化可用于编写一个特征,该特征将获得给定std::variant中使用的模板类型列表:

    #include <variant>
    
    template<class T>
    struct t_variant_cat;
    
    template<class ... Old>
    struct t_variant_cat<std::variant<Old...>> {
        // Old is a parameter pack containing all of 
        //  template arguments of the std::variant
    };
    

    接下来我们添加另一个模板参数,指定要添加的类型并为这个新类型定义一个别名。

    #include <variant>
    
    template<class T, class New>
    struct t_variant_cat;
    
    template<class ... Old, class New>
    struct t_variant_cat<std::variant<Old...>, New> {
        using type = std::variant<Old..., New>;
    };
    

    typename t_variant_cat&lt;my_variant, Class4&gt;::type 现在应该产生std::variant&lt;Class1, Class2, Class3, Class4&gt;。为方便起见,我们可以添加一个类型别名,以避免每次都写 typename::type

    template<class Old, class New>
    using t_variant_cat_t = typename t_variant_cat<Old, New>::type;
    

    用法是:

    #include <variant>
    
    template<class T, class New>
    struct t_variant_cat;
    
    template<class ... Old, class New>
    struct t_variant_cat<std::variant<Old...>, New> {
        using type = std::variant<Old..., New>;
    };
    
    template<class Old, class New>
    using t_variant_cat_t = typename t_variant_cat<Old, New>::type;
    
    using old = std::variant<int, float>;
    using extended = t_variant_cat_t<old, double>;
    
    // Makes sure this actually works
    static_assert(std::is_same_v<extended, std::variant<int, float, double>>,
        "Something is wrong.");
    

    【讨论】:

    • 该问题被错误地标记为C++14(尽管std::variant 仅存在于C++17 中)。假设您可以通过使用std::is_same_v 而不是std::is_same&lt;&gt;::value 从示例中截取7 个字节,并且通过不定义t_variant_cat 截取另外2 个字节,它只需要声明:-D
    • @bobah 感谢您的反馈。我会做出改变的。
    【解决方案2】:

    godbolted

    #include <variant>
    
    template <typename T, typename... Args> struct concatenator;
    
    template <typename... Args0, typename... Args1>
    struct concatenator<std::variant<Args0...>, Args1...> {
        using type = std::variant<Args0..., Args1...>;
    };
    
    int main() {
        using type_t = std::variant<int, char, double>;
        static_assert(
            std::is_same_v<
                concatenator<type_t, float, short>::type,
                std::variant<int, char, double, float, short>>);
        return 0;
    }
    

    【讨论】:

    • 简单明了。我很惊讶最复杂的答案比这更多。
    • @CássioRenan - 我知道!
    • 有趣。我猜,这使得std::variant 变成了一个具有身份std::variant&lt;&gt; 的非交换幺半群。
    • @Sebastian - 让我在谷歌上搜索monoid 是什么(最终),但同意,原则上,就像任何其他可变参数模板一样。
    【解决方案3】:
    namespace impl_details {
      template<class Var, class...Ts>
      struct extend_type;
      template<template<class...>class Z, class...Vs, class...Ts>
      struct extend_type<Z<Vs...>, Ts...> {
        using type=Z<Vs..., Ts...>;
      };
    }
    template<class Var, class...Ts>
    using extend_type = typename impl_details::extend_type<Var, Ts...>::type;
    

    现在

    extend_type<my_variant, Class4>
    

    std::variant<Class1, Class2, Class3, Class4>
    

    虽然我不赞成您基于 1 的索引。

    extend_type&lt; std::tuple&lt;a,b,c&gt;, d, e, f &gt; 也可以。

    我可以玩这个...

    namespace impl_details {
      template<class Lhs, class Rhs>
      struct type_cat;
      template<template<class...>class Z, class...Lhs, class...Rhs>
      struct type_cat<Z<Lhs...>, Z<Rhs...>> {
        using type=Z<Lhs..., Rhs...>;
      };
    }
    template<class Lhs, class Rhs>
    using type_cat = typename impl_details::type_cat<Lhs, Rhs>::type;
    
    
    auto variant_trinary( bool b ) {
      return [b](auto&& lhs, auto&& rhs) {
        using R=type_cat< std::decay_t<decltype(lhs)>, std::decay_t<decltype(rhs)> >;
        auto as_R = [](auto&&x)->R{ return decltype(x)(x)); };
        if (b)
          return std::visit( as_R, lhs );
        else
          return std::visit( as_R, rhs );
      };
    }
    

    这为我们提供了两个变体的三元运算符。

    auto var = variant_trinary(bool_expr)( var1, var2 );
    

    其中varvar1var2 的变体类型的串联。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-16
      • 2015-04-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多