【问题标题】:Create cartesian product expansion of two variadic, non-type template parameter packs创建两个可变参数、非类型模板参数包的笛卡尔积扩展
【发布时间】:2017-10-19 13:53:42
【问题描述】:

可以说,我有

  • 两个非类型模板参数列表(可能有不同的类型)
  • 一个模板foo,它将每个列表的一个值作为参数

如何创建foos 的可变参数包,使用两个列表元素的笛卡尔积进行参数化?

这就是我的意思:

template<int ...>
struct u_list {};

template<char ...>
struct c_list {};

template<int, char >
struct foo {};

template<class ...>
struct bar {};

using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;


using result_t = /* magic happens*/
using ref_t = bar<
    foo<1, -3>, foo<1, 3>,
    foo<5, -3>, foo<5, 3>,
    foo<7, -3>, foo<7, 3>
>;

static_assert(std::is_same<result_t, ref_t >::value, "");

我正在寻找一种适用于 c++11 并且不使用除 c++11 标准库之外的任何库的解决方案。我也有我的 c++14 的 index_sequence / make_index_sequence 的处理版本,如果这样可以简化代码,可以将非类型参数列表提供为数组。

到目前为止我发现的最接近的是:How to create the Cartesian product of a type list?。所以原则上(我没有测试过)应该可以将非类型参数包转换为类型参数包,然后在链接的帖子中应用解决方案,但我希望有一个更简单/更短的解决方案这行:

template<int... Ints, char ... Chars>
auto magic(u_list<Ints...>, c_list<Chars...>) 
{
    //Doesn't work, as it tries to expand the parameter packs in lock step
    return bar<foo<Ints,Chars>...>{};  
}

using result_t = decltype(magic(int_vals{}, char_vals{}));

【问题讨论】:

  • 我一直在用参数组合做一些讨厌的事情......也许这会有所帮助:[click me]

标签: c++ c++11 variadic-templates template-meta-programming cartesian-product


【解决方案1】:

您可以执行以下操作:

template <int... Is>
using u_list = std::integer_sequence<int, Is...>;

template <char... Cs>
using c_list = std::integer_sequence<char, Cs...>;

template<int, char> struct foo {};

template<class ...> struct bar {};

template <std::size_t I, typename T, template <typename, T...> class C, T ... Is>
constexpr T get(C<T, Is...> c)
{
    constexpr T values[] = {Is...};
    return values[I];
}


template <std::size_t I, typename T>
constexpr auto get_v = get<I>(T{});


template<int... Ints, char ... Chars, std::size_t ... Is>
auto cartesian_product(u_list<Ints...>, c_list<Chars...>, std::index_sequence<Is...>)
-> bar<foo<
        get_v<Is / sizeof...(Chars), u_list<Ints...> >,
        get_v<Is % sizeof...(Chars), c_list<Chars...> >
        >...
    >;

template<int... Ints, char ... Chars>
auto cartesian_product(u_list<Ints...> u, c_list<Chars...> c)
-> decltype(cartesian_product(u, c, std::make_index_sequence<sizeof...(Ints) * sizeof...(Chars)>()));




using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;

using result_t = decltype(cartesian_product(int_vals{}, char_vals{}));

Demo

std部分的可能实现:

template <typename T, T ... Is> struct integer_sequence{};

template <std::size_t ... Is>
using index_sequence = integer_sequence<std::size_t, Is...>;

template <std::size_t N, std::size_t... Is>
struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...> {};

template <std::size_t... Is>
struct make_index_sequence<0u, Is...> : index_sequence<Is...> {};

并改变答案:

template <std::size_t I, typename T, template <typename, T...> class C, T ... Is>
constexpr T get(C<T, Is...> c)
{
    using array = T[];
    return array{Is...}[I];
}

template<int... Ints, char ... Chars, std::size_t ... Is>
auto cartesian_product(u_list<Ints...>, c_list<Chars...>, index_sequence<Is...>)
-> bar<foo<
        get<Is / sizeof...(Chars)>(u_list<Ints...>{}),
        get<Is % sizeof...(Chars)>(c_list<Chars...>{})
        >...
    >;

Demo C++11

【讨论】:

  • 谢谢。乍一看,这似乎是解决方案,对我来说最具可读性。我将不得不对真实代码进行一些试验,看看哪种答案最适合我。
  • 你能把它翻译成 c++11 解决方案吗?
  • @MikeMB:添加了 C++11 版本。
【解决方案2】:

在我看来,在纯类型领域进行模板元编程要容易得多。

从非类型模板参数领域转移到类型领域并再次返回需要一些工作,但这意味着您使用的是通用元编程实用程序而不是特定于您的问题的实用程序。


所以我会将您的问题简化为类型列表中的笛卡尔积。

这是我的类型包:

template<class...Ts>struct types {
  using type=types; // makes inheriting from it useful
  static constexpr std::size_t size = sizeof...(Ts);
};

首先我们写fmap。 Fmap 接受一个函数和一个列表,并返回列表中应用了函数的每个元素的列表。

template<template<class...>class Z, class List>
struct fmap {};
template<template<class...>class Z, class List>
using fmap_t = typename fmap<Z,List>::type;
template<template<class...>class Z, class...Ts>
struct fmap<Z, types<Ts...>>:
  types<Z<Ts>...>
{};

fapply。 fapply 也接受一个函数和一个列表,但将该函数应用于整个列表元素集。

template<template<class...>class Z, class List>
struct fapply {};
template<template<class...>class Z, class List>
using fapply_t=typename fapply<Z,List>::type;
template<template<class...>class Z, class...Ts>
struct fapply<Z, types<Ts...>> {
  using type=Z<Ts...>;
};

碰巧,fapply 的部分应用非常有用:

template<template<class...>class Z>
struct applier {
    template<class List>
    using apply = fapply_t<Z,List>;
};

我们希望能够连接列表:

template<class...>
struct cat:types<> {};
template<class...As, class...Bs, class...Cs>
struct cat<types<As...>, types<Bs...>, Cs...>:
    cat<types<As..., Bs...>, Cs...>
{};
template<class...As>
struct cat<types<As...>>:types<As...>{};
template<class...Ts>using cat_t=typename cat<Ts...>::type;

那么,这里是 cart_product_t:

template<class A, class B>
struct cart_product {};
template<class A, class B>
using cart_product_t = typename cart_product<A,B>::type;
template<class A, class... Bs>
struct cart_product<types<A>, types<Bs...>>:
  types< types<A, Bs>... >
{};
// reduce cart_product to cart_product on a one element list on the lhs:
template<class...As, class... Bs>
struct cart_product<types<As...>, types<Bs...>>:
  fapply_t<
    cat_t,
    fmap_t<
      applier<cart_product_t>::template apply,
      types<
        types< types<As>, types<Bs...> >...
      >
    >
  >
{};

特定于您的问题的类型:

template<int...>struct u_list {};
template<char...>struct c_list {};
template<int, char>struct foo {};
template<class...>struct bar{};

一种将值列表提升为类型的工具:

template<class> struct lift {};
template<int...is> struct lift<u_list<is...>>:
  types< std::integral_constant<int, is>... >
{};
template<char...is> struct lift<c_list<is...>>:
  types< std::integral_constant<char, is>... >
{};
template<class T>using lift_t=typename lift<T>::type;

lower_to_foo 接受一对类型,并将它们转换为 foo:

template<class I, class C>
using lower_to_foo = foo<I::value, C::value>;

现在我们把它们放在一起:

using int_vals = u_list<1, 5, 7>;
using char_vals = c_list<-3, 3>;

using product = cart_product_t< lift_t<int_vals>, lift_t<char_vals> >;
static_assert( product::size == 6, "should be 6" );
using result_t = fapply_t< bar, fmap_t< applier<lower_to_foo>::template apply, product > >;

using ref_t = bar<
  foo<1, -3>, foo<1, 3>,
  foo<5, -3>, foo<5, 3>,
  foo<7, -3>, foo<7, 3>
>;
ref_t test = result_t{}; // gives better error messages than static_assert
static_assert(std::is_same<result_t, ref_t >::value, "");

鲍勃是你的叔叔。

catfmapfapply都是函数式编程中比较标准的函数。 applier 只是让您编写模板映射函数,使用元素而不是列表(它是部分应用的fapply)。

Live example.


现在,还记得我说过模板元编程使用类型更容易吗?

注意到所有这些模板模板参数了吗?如果它们是类型会更容易。

template<template<class...>class Z>
struct ztemplate {
  template<class...Ts>using apply=Z<Ts...>;
};

您可以一直使用类型标签和 operator() on ztemplate 和其他有趣的方式进行 hana 风格的 constexpr 元编程。

【讨论】:

  • std::integral_constant 实际上是否必须具有整数类型或者是例如类枚举也可以吗?
  • @MikeMB Mate,函数指针甚至是可能的。它们甚至可以在 C++14 中调用。
  • 谢谢。这似乎是一个很好的解决方案,但除非我们在代码中需要更多的 tmp,否则我有点不愿意为我的单个用例添加这么多通用工具。
【解决方案3】:

以类型列表叉积为基础

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

template<int ...> struct u_list {};

template<char ...> struct c_list {};

template<int, char > struct foo {};

template<typename...> struct type_list {};

我们将char... 包扩展为row

template<int I, char... Cs>
  struct row
{
  typedef type_list<foo<I,Cs>...> type;
};

template <typename... T> struct concat;

template <typename... S, typename... T>
struct concat<type_list<S...>, type_list<T...>>
{
    using type = type_list<S..., T...>;
};

我们想要一个额外的专业化 concat 打破基本情况

template <typename... T>
struct concat<type_list<T...>, void>
{
    using type = type_list<T...>;
};

template<typename I, typename C>
struct cross_product;

基本情况:不再有整数

template<char... Cs>
struct cross_product<u_list<>, c_list<Cs...>>
{
    using type = void;
};

递归案例:一个int,后跟一组int

template<int I, int... Is, char... Cs>
struct cross_product<u_list<I, Is...>, c_list<Cs...>>
{
    using type = typename concat<typename row<I,Cs...>::type, typename cross_product<u_list<Is...>, c_list<Cs...>>::type>::type;

};

int main()
{
  using int_vals = u_list<1, 5, 7>;
  using char_vals = c_list<-3, 3>;

  using result_t = cross_product<int_vals, char_vals>::type;
  using ref_t = type_list<
      foo<1, -3>, foo<1, 3>,
      foo<5, -3>, foo<5, 3>,
      foo<7, -3>, foo<7, 3>
  >;

  static_assert(std::is_same<result_t, ref_t >::value, "");
  return 0;
}

Live on Coliru!

【讨论】:

    【解决方案4】:

    以下是我的 2 美分...

    如果你想要一个通用的解决方案,我看到的更大的问题是从 int_valschar_vals 类型并不容易(在 C++11 中;在 C++17 中更简单)提取包含的类型值(intchar)。

    所以我想你必须将它们与foobar 一起传递给magic&lt;&gt;(如果你不希望foobar 硬编码)。

    所以对magic&lt;&gt; 的调用变成(以我的方式)

    using result_t
       = typename magic<int, char, foo, bar, int_vals, char_vals>::type; 
    

    以下是我的解决方案的完整工作示例。

    #include <type_traits>
    
    template <int...>  struct u_list {};
    template <char...> struct c_list {};
    
    template <int, char>    struct foo {};
    template <typename ...> struct bar {};
    
    template <typename T1, typename T2, T1 t1, T2 ... T2s>
    struct midProd
     { };
    
    template <typename T1, typename T2, template <T1, T2> class, typename...>
    struct magicHelper;
    
    template <typename T1, typename T2,
              template <T1, T2> class ResIn,
              template <typename...> class ResOut,
              typename ... R>
    struct magicHelper<T1, T2, ResIn, ResOut<R...>>
     { using type = ResOut<R...>; };
    
    template <typename T1, typename T2,
              template <T1, T2> class ResIn,
              template <typename...> class ResOut,
              typename ... R, T1 ts1, T2 ... ts2, typename ... MpS>
    struct magicHelper<T1, T2, ResIn, ResOut<R...>,
                  midProd<T1, T2, ts1, ts2...>, MpS...>
     { using type = typename magicHelper<T1, T2, ResIn,
                       ResOut<R..., ResIn<ts1, ts2>...>, MpS...>::type; };
    
    
    template <typename T1, typename T2,
              template <T1, T2> class,
              template <typename...> class,
              typename, typename>
    struct magic;
    
    template <typename T1, typename T2,
              template <T1, T2> class ResIn,
              template <typename...> class ResOut,
              template <T1...> class C1, template <T2...> class C2,
              T1 ... ts1, T2 ... ts2>
    struct magic<T1, T2, ResIn, ResOut, C1<ts1...>, C2<ts2...>>
     { using type = typename magicHelper<T1, T2, ResIn, ResOut<>,
                       midProd<T1, T2, ts1, ts2...>...>::type ; };
    
    int main ()
     {
       using int_vals  = u_list<1, 5, 7>;
       using char_vals = c_list<-3, 3>;
    
       using result_t
          = typename magic<int, char, foo, bar, int_vals, char_vals>::type;
    
       using ref_t = bar< foo<1, -3>, foo<1, 3>,
                          foo<5, -3>, foo<5, 3>,
                          foo<7, -3>, foo<7, 3> >;
    
       static_assert(std::is_same<result_t, ref_t >::value, "");
     }
    

    显然,如果您更喜欢硬编码某些类型(u_listc_listfoobar),解决方案会变得简单得多

    #include <type_traits>
    
    template <int...>  struct u_list {};
    template <char...> struct c_list {};
    
    template <int, char>    struct foo {};
    template <typename ...> struct bar {};
    
    template <int, char...> struct midProd {};
    
    template <typename...>
    struct magicH;
    
    template <typename ... R>
    struct magicH<bar<R...>>
     { using type = bar<R...>; };
    
    template <typename ... R, int i, char ... cs, typename ... MpS>
    struct magicH<bar<R...>, midProd<i, cs...>, MpS...>
     { using type = typename magicH<bar<R..., foo<i, cs>...>, MpS...>::type; };
    
    
    template <typename, typename>
    struct magic;
    
    template <int ... is, char ... cs>
    struct magic<u_list<is...>, c_list<cs...>>
     { using type = typename magicH<bar<>, midProd<is, cs...>...>::type; };
    
    int main ()
     {
       using int_vals  = u_list<1, 5, 7>;
       using char_vals = c_list<-3, 3>;
    
       using result_t = typename magic<int_vals, char_vals>::type;
    
       using ref_t = bar< foo<1, -3>, foo<1, 3>,
                          foo<5, -3>, foo<5, 3>,
                          foo<7, -3>, foo<7, 3> >;
    
       static_assert(std::is_same<result_t, ref_t >::value, "");
     }
    

    【讨论】:

      【解决方案5】:

      与 C++17 中的其他内容相同:

      // Type your code here, or load an example.
      #include <type_traits>
      
      template<int ...>
      struct u_list {};
      
      template<char ...>
      struct c_list {};
      
      template<int, char >
      struct foo {};
      
      template<class ...>
      struct bar {};
      
      using int_vals = u_list<1, 5, 7>;
      using char_vals = c_list<-3, 3>;
      
      template<class... Args> struct type_list{
          template<class> struct make_concat;
          template<class ...Xs>
          struct make_concat<type_list<Xs...>>{
              using type = type_list<Args...,Xs...>;
          };
          template<class T>
          using concat = typename make_concat<T>::type;
          template<template<class...>class TT>
          using applied_to = TT<Args...>;
      };
      
      template<
               template<auto,auto> class C
              ,class X,class Y,class Yit=Y>
      struct cart_prod;
      template<template<auto,auto> class C,
               template<auto...> class Xt,
               template<auto...> class Yt,
               class Yit,
               auto Xi,auto...Xis,auto Yi,auto...Yis>
      struct cart_prod<C,Xt<Xi,Xis...>,Yt<Yi,Yis...>,Yit>{
          using type = typename type_list<class C<Xi,Yi>>
             ::template concat<typename cart_prod<C,Xt<Xi,Xis...>,Yt<Yis...>,Yit>::type>;
      };
      template<template<auto,auto> class C,
               template<auto...> class Xt,
               template<auto...> class Yt,
               class Yit,
               auto Xi,auto...Xis,auto Yi>
      struct cart_prod<C,Xt<Xi,Xis...>,Yt<Yi>,Yit>{
          using type = typename type_list<class C<Xi,Yi>>
             ::template concat<typename cart_prod<C,Xt<Xis...>,Yit,Yit>::type>;
      };
      template<template<auto,auto> class C,
               template<auto...> class Xt,
               template<auto...> class Yt,
               class Yit,
               auto Xi,auto Yi>
      struct cart_prod<C,Xt<Xi>,Yt<Yi>,Yit>{
          using type = type_list<class C<Xi,Yi>>;
      };
      
      
      using result_t = cart_prod<foo,int_vals,char_vals>::type::applied_to<bar>;
      using ref_t = bar<
          foo<1, -3>, foo<1, 3>,
          foo<5, -3>, foo<5, 3>,
          foo<7, -3>, foo<7, 3>
      >;
      
      static_assert(std::is_same<result_t, ref_t >::value, "");
      

      【讨论】:

        【解决方案6】:

        另一个(但更短的)解决方案可能是

        template<typename Ret,typename R>
        auto magic( bar<u_list<>, R>, Ret result, R ) { return result; }
        
        template<int I, int... Ints, typename... Foos, typename R>
        auto magic( bar<u_list<I,Ints...>, c_list<>>, bar<Foos...>, R rollback ) { return magic(
            bar<u_list<Ints...>,R>{}, bar<Foos...>{}, rollback );}
        
        template<int I, int... Ints, char J, char ... Chars, typename... Foos, typename R >
        auto magic( bar<u_list<I,Ints...>, c_list<J,Chars...>>, bar<Foos...>, R rollback ) { return magic(
            bar<u_list<I,Ints...>, c_list<Chars...>>{},
            bar<Foos...,foo<I,J>>{},
            rollback );}
        
        using result_t = decltype(magic( bar<int_vals,char_vals>{}, bar<>{}, char_vals{} ));
        

        【讨论】:

          猜你喜欢
          • 2021-12-28
          • 1970-01-01
          • 1970-01-01
          • 2014-10-30
          • 2015-05-21
          • 1970-01-01
          • 2020-11-03
          • 2012-03-27
          相关资源
          最近更新 更多