I would like to 'generate' a jump table of function pointers. The functions which are pointed to are templated with two types. There should be a different function instanciated for every possible pair in two list of types. 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 when 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 an indirection (a first jump table which contains pointers to functions with another jump table), but I find it clumsy.
So, my question is: is there a workaround to this?
Your sample code is wrong, even in case that it compiles (i.e. when sizeof...(Xs) == sizeof...(Ys)). Say, you have N-ary tuples, then jump_table has N*N elements, but only first N elements are initialized with the function ptrs.
First, you need to inner join the 2 lists of types:
template<class A, class B>
struct Pair;
template<class... Ts>
struct List {};
template<class T, class... Ts>
using mul = List<Pair<T, Ts>...>;
template<class...>
struct cat;
template<class T>
struct cat<T>
{
using type = T;
};
template<class... As, class... Bs>
struct cat<List<As...>, List<Bs...>>
{
using type = List<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<List<As...>, List<Bs...>>
{
using type = typename cat<mul<As, Bs...>...>::type;
};
for example,
join<List<int[1], int[2]>, List<float[1], float[2], float[3]>>::type
gives you
List<Pair<int[1], float[1]>, Pair<int[1], float[2]>, Pair<int[1], float[3]>, Pair<int[2], float[1]>, Pair<int[2], float[2]>, Pair<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(List<Pair<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<List<Xs...>, List<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.