Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expand parameter packs with different lengths

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?

like image 693
Alexandre Hamez Avatar asked Sep 04 '14 03:09

Alexandre Hamez


2 Answers

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 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;
};

for example,

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.

like image 193
Jamboree Avatar answered Sep 28 '22 05:09

Jamboree


The other answers here seem much too complex for the problem at hand. Here's how I'd 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 its C++14 mode.

like image 45
Richard Smith Avatar answered Sep 28 '22 05:09

Richard Smith