Expand parameter packages of different lengths

I would like to "generate" a transition table of function pointers. The functions that are referenced are templates with two types. For each possible pair, a different function must exist in the two type lists. Ideally, we could have something like:

#include <tuple> template <typename X, typename Y> void foo() {} template <typename... Xs, typename... Ys> void bar(const std::tuple<Xs...>&, const std::tuple<Ys...>&) { using fun_ptr_type = void (*) (void); static constexpr fun_ptr_type jump_table[sizeof...(Xs) * sizeof...(Ys)] = {&foo<Xs, Ys>...}; } int main () { using tuple0 = std::tuple<int, char, double>; using tuple1 = std::tuple<float, unsigned long>; bar(tuple0{}, tuple1{}); } 

As expected, it fails if the tuples have different lengths:

 foo.cc:15:20: error: pack expansion contains parameter packs 'Xs' and 'Ys' that have different lengths (3 vs. 2) = {&foo<Xs, Ys>...}; ~~ ~~ ^ foo.cc:23:3: note: in instantiation of function template specialization 'bar<int, char, double, float, unsigned long>' requested here bar(tuple0{}, tuple1{}); ^ 1 error generated. 

To achieve this kind of functionality, I already tried and succeeded with a pointer (the first transition table containing pointers to functions with another jump table), but I find it awkward.

So my question is: are there any workarounds?

+5
source share
4 answers

Your sample code is incorrect even if it compiles (i.e. when sizeof ... (Xs) == sizeof ... (Ys)). Say you have N-ary tuples, then jump_table has N * N elements, but only the first N elements are initialized by the ptrs function.

First, you need to combine two type lists:

 template<class A, class B> struct P; template<class... Ts> struct L {}; template<class T, class... Ts> using mul = L<P<T, Ts>...>; template<class...> struct cat; template<class T> struct cat<T> { using type = T; }; template<class... As, class... Bs> struct cat<L<As...>, L<Bs...>> { using type = L<As..., Bs...>; }; template<class A, class B, class... Ts> struct cat<A, B, Ts...> { using type = typename cat<typename cat<A, B>::type, Ts...>::type; }; template<class A, class B> struct join; template<class... As, class... Bs> struct join<L<As...>, L<Bs...>> { using type = typename cat<mul<As, Bs...>...>::type; }; 

eg,

 join<L<int[1], int[2]>, L<float[1], float[2], float[3]>>::type 

gives you

 L<P<int[1], float[1]>, P<int[1], float[2]>, P<int[1], float[3]>, P<int[2], float[1]>, P<int[2], float[2]>, P<int[2], float[3]> 

Back to your example:

 template <typename X, typename Y> void foo() {} template<class T, std::size_t N> struct jump_table { template<class... As, class... Bs> constexpr jump_table(L<P<As, Bs>...>) : table{&foo<As, Bs>...} {} T table[N]; }; template <typename... Xs, typename... Ys> void bar(const std::tuple<Xs...>&, const std::tuple<Ys...>&) { using fun_ptr_type = void (*) (void); static constexpr jump_table<fun_ptr_type, sizeof...(Xs) * sizeof...(Ys)> table = {typename join<L<Xs...>, L<Ys...>>::type()}; } int main () { using tuple0 = std::tuple<int, char, double>; using tuple1 = std::tuple<float, unsigned long>; bar(tuple0{}, tuple1{}); } 

This should do what you expected.

+4
source

The other answers here seem too complicated for this problem. Here's how I do it:

 #include <array> #include <tuple> template <typename X, typename Y> void foo() {} using fun_ptr_type = void (*) (void); // Build one level of the table. template <typename X, typename ...Ys> constexpr std::array<fun_ptr_type, sizeof...(Ys)> jump_table_inner = {{&foo<X, Ys>...}}; // Type doesn't matter, we're just declaring a primary template that we're // about to partially specialize. template <typename X, typename Y> void *jump_table; // Build the complete table. template <typename ...Xs, typename ...Ys> constexpr std::array<std::array<fun_ptr_type, sizeof...(Ys)>, sizeof...(Xs)> jump_table<std::tuple<Xs...>, std::tuple<Ys...>> = {jump_table_inner<Xs, Ys...>...}; int main () { using tuple0 = std::tuple<int, char, double>; using tuple1 = std::tuple<float, unsigned long>; // Call function for (int, float). jump_table<tuple0, tuple1>[0][0](); } 

This is accepted by Clang 3.5 in C ++ 14 mode.

+3
source

My usual solution for expanding the product context( f<Xs, Ys>... ) /* not what we want */ is to rewrite it to context2( g<Xs, Ys...>... ) . Bearing in mind that g is responsible for the extension of Ys with respect to some X , and the final extension performs g for all Xs . The consequence of this rewriting is that we introduce an additional attachment, thus different contexts.

In our case, instead of a flat array of function pointers, we have an array of function pointer arrays. Unlike the solution you tried, although it really is a function tag &foo<X, Y> that we care about - and alignment is simple.

 #include <cassert> #include <utility> #include <array> template<typename X, typename Y> void foo() {} using foo_type = void(*)(); template<typename... T> struct list { static constexpr auto size = sizeof...(T); }; template<typename X, typename Y, typename Indices = std::make_index_sequence<X::size * Y::size>> struct dispatch; template< template<typename...> class XList, typename... Xs , template<typename...> class YList, typename... Ys , std::size_t... Indices > struct dispatch<XList<Xs...>, YList<Ys...>, std::index_sequence<Indices...>> { private: static constexpr auto stride = sizeof...(Ys); using inner_type = std::array<foo_type, stride>; using multi_type = inner_type[sizeof...(Xs)]; template<typename X, typename... Yss> static constexpr inner_type inner() { return {{ &foo<X, Yss>... }}; } static constexpr multi_type multi_value = { inner<Xs, Ys...>()... }; public: static constexpr auto size = sizeof...(Xs) * sizeof...(Ys); static constexpr foo_type value[size] = { multi_value[Indices / stride][Indices % stride]... }; }; template< template<typename...> class XList, typename... Xs , template<typename...> class YList, typename... Ys , std::size_t... Indices > constexpr foo_type dispatch<XList<Xs...>, YList<Ys...>, std::index_sequence<Indices...>>::value[size]; int main() { using dispatch_t = dispatch< list<int, char, double>, list<float, unsigned long> >; constexpr auto&& table = dispatch_t::value; static_assert( dispatch_t::size == 6, "" ); static_assert( table[0] == &foo<int, float>, "" ); static_assert( table[1] == &foo<int, unsigned long>, "" ); static_assert( table[2] == &foo<char, float>, "" ); static_assert( table[3] == &foo<char, unsigned long>, "" ); static_assert( table[4] == &foo<double, float>, "" ); static_assert( table[5] == &foo<double, unsigned long>, "" ); } 

Demo version of Coliru .

+2
source

What you actually have is more like a zip from two lists ( <X1,Y1> , <X2,Y2> , ...), which does not work when the lists are of different lengths.

To calculate the β€œproduct” of two, I think you need to use helper classes to make it work. See another question, like yours: How to create a Cartesian product of a list of types?

+1
source

Source: https://habr.com/ru/post/1201788/


All Articles