How to create the Cartesian product of a type list?

前端 未结 9 1391
夕颜
夕颜 2020-11-28 09:15

I\'d like to create the cross product of a list of types using variadic templates.

Here\'s what I have so far:

#include 
#include <         


        
相关标签:
9条回答
  • 2020-11-28 09:53

    Really enjoyed this "homework" assignment :)

    Both solutions below create a class full of type_list typedefs, along with member functions that will check to see if a given list of types exist in the class as a type_list.

    The first solution creates all possible combinations of types from 1 to N types per type_list (the width parameter defines N). The second solution creates only pairs of types.

    First Solution

    template<typename... Ts> struct type_list { typedef type_list<Ts...> type; };
    
    template<size_t, typename...> struct xprod_tlist_ {};
    
    template<typename... Ts, typename... Us>
    struct xprod_tlist_<1, type_list<Ts...>, Us...> {};
    
    template<size_t width, typename... Ts, typename... Us>
    struct xprod_tlist_<width, type_list<Ts...>, Us...>
    : type_list<Ts..., Us>...
    , xprod_tlist_<width - 1, type_list<Ts..., Us>, Us...>... {};
    
    template<size_t width, typename... Ts> struct xprod_tlist
    : type_list<Ts>..., xprod_tlist_<width, type_list<Ts>, Ts...>... {
        template<typename... Us> struct exists
        : std::is_base_of<type_list<Us...>, xprod_tlist<width, Ts...>> {};
    
        template<typename... Us> struct assert_exists {
            static_assert(exists<Us...>::value, "Type not present in list");
        };
    };
    

    Usage:

    typedef xprod_tlist<5, int, char, string, float, double, long> X;
    
    //these pass
    X::assert_exists<int, int, int, int, int> assert_test1;
    X::assert_exists<double, float, char, int, string> assert_test2;
    
    //these fail
    X::assert_exists<char, char, char, char, char, char> assert_test3;
    X::assert_exists<int, bool> assert_test4;
    
    //true
    auto test1 = X::exists<int, int, int, int, int>::value;
    auto test2 = X::exists<double, float, char, int, string>::value;
    
    //false
    auto test3 = X::exists<char, char, char, char, char, char>::value;
    auto test4 = X::exists<int, bool>::value;
    

    Second Solution

    template<class T, class U> struct type_pair { typedef type_pair<T, U> type; };
    template<class... Ts> struct type_list {};
    template<class...> struct xprod_tlist_ {};
    
    template<class T, class... Ts, class... Us>
    struct xprod_tlist_<type_list<T, Ts...>, type_list<Us...>>
    : type_pair<T, Us>..., xprod_tlist_<type_list<Ts...>, type_list<Us...>> {};
    
    template<class... Ts>
    struct xprod_tlist : xprod_tlist_<type_list<Ts...>, type_list<Ts...>> {
        template<class T, class U> struct exists
        : std::is_base_of<type_pair<T, U>, xprod_tlist<Ts...>> {};
    
        template<class T, class U> struct assert_exists {
            static_assert(exists<T, U>::value, "Type not present in list");
        };
    };
    

    Usage:

    typedef xprod_tlist<int, float, string> X;
    
    //these pass
    X::assert_exists<int, int> assert_test1;
    X::assert_exists<int, float> assert_test2;
    X::assert_exists<int, string> assert_test3;
    X::assert_exists<float, int> assert_test4;
    X::assert_exists<float, float> assert_test5;
    X::assert_exists<float, string> assert_test6;
    X::assert_exists<string, int> assert_test7;
    X::assert_exists<string, float> assert_test8;
    X::assert_exists<string, string> assert_test9;
    
    //this fails
    X::assert_exists<int, char> assert_test10;
    
    //true
    auto test1 = X::exists<int, int>::value;
    auto test2 = X::exists<int, float>::value;
    auto test3 = X::exists<int, string>::value;
    auto test4 = X::exists<float, int>::value;
    auto test5 = X::exists<float, float>::value;
    auto test6 = X::exists<float, string>::value;
    auto test7 = X::exists<string, int>::value;
    auto test8 = X::exists<string, float>::value;
    auto test9 = X::exists<string, string>::value;
    
    //false
    auto test10 = X::exists<int, char>::value;
    
    0 讨论(0)
  • 2020-11-28 09:57

    C++17

    Working Demo

    Logic to concatenate type_lists to avoid nested type_list like you are asking for:

    // base case: 2 type_lists
    template<class... Ts, class... Us>
    auto concat(type_list<Ts...>, type_list<Us...>) -> type_list<Ts..., Us...>;
    
    // recursive case: more than 2 type_lists
    template<class... Ts, class... Us, class... Rest>
    auto concat(type_list<Ts...>, type_list<Us...>, Rest...) -> decltype(concat(type_list<Ts..., Us...>{}, Rest{}...));
    

    Note that these functions don't have (or need) implementations; this is a trick to avoid class template specialization (I learned it from Hana Dusikova's compile time regular expressions)

    Then, simplifying your row and cross_product impls as pairs and cross_product_impl, respectively:

    template<class T, class... Ts>
    using pairs = type_list<type_pair<T, Ts>...>;
    
    template<class... T>
    auto cross_product_impl()
    {
        if constexpr(sizeof...(T) == 0)
            return type_list<> {};
        if constexpr(sizeof...(T) == 1)
            return type_list<type_pair<T, T>...>{};
        if constexpr(sizeof...(T) > 1)
            return concat(pairs<T, T...>{}...);
    }
    

    if constexpr allows us to more easily express the logic, I think.

    Finally a type alias for cross_product that gives us what the type would be if we theoretically invoked cross_product_impl:

    template<class... T>
    using cross_product = decltype(cross_product_impl<T...>());
    

    Usage basically the same as before:

    cross_product<int, float, short> result;
    
    0 讨论(0)
  • 2020-11-28 10:03

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

    using input = type_list<int, float, short>;
    using result = mp_product<
        type_pair,
        input, input>;
    

    Demo.


    We can generalize this to picking N things, with repetition, from that input. We can't use type_pair anymore to group our elements, so we'll just have a list of type_list of elements. To do that, we basically need to write:

    mp_product<type_list, input, input, ..., input>
    //                    ~~~~~~~ N times ~~~~~~~~
    

    Which is also the same as:

    mp_product_q<mp_quote<type_list>, input, input, ..., input>
    //                                ~~~~~~~ N times ~~~~~~~~
    

    One way to do that is:

    template <int N>
    using product = mp_apply<
        mp_product_q,
        mp_append<
            mp_list<mp_quote<type_list>>, 
            mp_repeat_c<mp_list<input>, N>
            >>;
    

    Demo.

    0 讨论(0)
  • 2020-11-28 10:04

    So far all solutions have drawbacks, unnecessary dependencies, unnecessary helpers and all are restricted to the Cartesian power of two. The following solution has no such drawbacks and supports:

    1. Any cartesian power including 0.
    2. Returning the empty set if any of the factors is an empty set.
    3. The code is self contained and does not depend on any include files.
    4. The inputs of the function can be of any template type.
    5. The type of the output list can be specified via the first template parameter.

    It was actually to harder to implement (but good as homework) then I thought. I am actually thinking about creating a little generator which allows me an extended template syntax which makes these things really easy.

    Simplified the code works as follows: product converts an input list tuple<A...>,tuple<B...>,tuple<C...> into tuple<tuple<A>...>, tuple<B...>, tuple<C...>. This second list is then passed to product_helper which does the recursive Cartesian product computation.

    template <typename... T> struct cat2;
    
    template <template<typename...> class R, typename... As, typename... Bs>
    struct cat2 <R<As...>, R<Bs...> > {
            using type = R <As..., Bs...>;
    };
    
    template <typename... Ts> struct product_helper;
    
    template <template<typename...> class R, typename... Ts>
    struct product_helper < R<Ts...> > { // stop condition
            using type = R< Ts...>;
    };
    
    template <template<typename...> class R, typename... Ts>
    struct product_helper < R<R<> >, Ts... > { // catches first empty tuple
            using type = R<>;
    };
    
    template <template<typename...> class R, typename... Ts, typename... Rests>
    struct product_helper < R<Ts...>, R<>, Rests... > { // catches any empty tuple except first
            using type = R<>;
    };
    
    template <template<typename...> class R, typename... X, typename H, typename... Rests>
    struct product_helper < R<X...>, R<H>, Rests... > {
            using type1 = R <typename cat2<X,R<H> >::type...>;
            using type  = typename product_helper<type1, Rests...>::type;
    };
    
    template <template<typename...> class R, typename... X, template<typename...> class Head, typename T, typename... Ts, typename... Rests>
    struct product_helper < R<X...>, Head<T, Ts...>, Rests... > {
            using type1 = R <typename cat2<X,R<T> >::type...>;
            using type2 = typename product_helper<R<X...> , R<Ts...> >::type;
            using type3 = typename cat2<type1,type2>::type;
            using type  = typename product_helper<type3, Rests...>::type;
    };
    
    template <template<typename...> class R, typename... Ts> struct product;
    
    template <template<typename...> class R>
    struct product < R > { // no input, R specifies the return type
        using type = R<>;
    };
    
    template <template<typename...> class R, template<typename...> class Head, typename... Ts, typename... Tail>
    struct product <R, Head<Ts...>, Tail... > { // R is the return type, Head<A...> is the first input list
        using type = typename product_helper< R<R<Ts>...>, Tail... >::type;
    };
    

    Here is a compilable example of how the code can be used.

    0 讨论(0)
  • 2020-11-28 10:05

    Here's another solution.

    #include <iostream>
    #include <typeinfo>
    #include <cxxabi.h>
    
    template <typename ...Args> struct typelist { };
    template <typename, typename> struct typepair { };
    
    template <typename S, typename T> struct product;
    template <typename S, typename T> struct append;
    
    template<typename ...Ss, typename ...Ts>
    struct append<typelist<Ss...>, typelist<Ts...>> {
      typedef typelist<Ss..., Ts...> type;
    };
    
    template<>
    struct product<typelist<>, typelist<>> {
      typedef typelist<> type;
    };
    
    template<typename ...Ts>
    struct product<typelist<>, typelist<Ts...>> {
      typedef typelist<> type;
    };
    
    template<typename ...Ts>
    struct product<typelist<Ts...>, typelist<>> {
      typedef typelist<> type;
    };
    
    template<typename S, typename T, typename ...Ss, typename ...Ts>
    struct product<typelist<S, Ss...>, typelist<T, Ts...>> {
      typedef typename
              append<typelist<typepair<S, T>,
                              typepair<S, Ts>...,
                              typepair<Ss, T>...>,
            typename product<typelist<Ss...>, typelist<Ts...>>::type>::type type;
    };
    
    int main(void)
    {
      int s;
      std::cout << abi::__cxa_demangle(
      typeid(product<typelist<int, float>, typelist<short, double>>::type).name(), 0, 0, &s)     << "\n";
      return 0;
    }
    
    0 讨论(0)
  • 2020-11-28 10:07

    Somehow my brain is fried: I think I'm using more code than is needed but, at least, it has the desired results (although I didn't fix the memory leak):

    #include <iostream>
    #include <typeinfo>
    #include <cxxabi.h>
    
    template<typename...> struct type_list {};
    
    template<typename T1, typename T2> struct type_pair {};
    
    template<typename T, typename... Rest>
      struct row
    {
      typedef type_list<type_pair<T,Rest>...> type;
    };
    
    template <typename... T> struct concat;
    template <typename... S, typename... T>
    struct concat<type_list<S...>, type_list<T...>>
    {
        typedef type_list<S..., T...> type;
    };
    
    template <typename... T>
    struct expand
    {
        typedef type_list<T...> type;
    };
    template <> struct expand<> { typedef type_list<> type; };
    template <typename... T, typename... L>
    struct expand<type_list<T...>, L...>
    {
        typedef typename concat<typename expand<T...>::type, typename expand<L...>::type>::type type;
    };
    
    template<typename... T>
      struct cross_product
    {
        typedef typename expand<type_list<typename row<T,T...>::type...>>::type type;
    
    };
    
    int main()
    {
      int s;
      typedef cross_product<int, float, short>::type result;
      std::cout << abi::__cxa_demangle(typeid(result).name(), 0, 0, &s) << std::endl;
    
      return 0;
    }
    
    0 讨论(0)
提交回复
热议问题