5

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?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Alexandre Hamez
  • 7,725
  • 2
  • 28
  • 39

4 Answers4

4

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.

Jamboree
  • 5,139
  • 2
  • 16
  • 36
3

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.

Richard Smith
  • 13,696
  • 56
  • 78
  • I tried something similar before asking this question, but it doesn't compile with clang 3.4 or g++ 4.9... However, I agree, it's a much simpler solution. – Alexandre Hamez Sep 06 '14 at 08:25
  • 1
    I object to you describing my answer as ‘too complex’, seeing as we’re doing the same thing. I do it more verbosively and through class template specializations, and that’s for didactic purposes—I strive to make it possible for others to try the code on Coliru, so that they can make sense of my answer even when our compilers differ. Obviously a short answer made of mostly code has its merits too, but I think the off-hand remark is out of place. – Luc Danton Sep 07 '14 at 05:27
  • 1
    I don't disagree that your solution has more educational value; it exposes more techniques and works in more situations. But that's exactly what makes it too complex for the problem at hand. – Richard Smith Sep 15 '14 at 19:56
2

My normal solution for a product expansion context( f<Xs, Ys>... ) /* not what we want */ is to rewrite it to context2( g<Xs, Ys...>... ). Meaning that g is in charge of expanding Ys with respect to some X, and the final expansion performs g for all Xs. A consequence of such a rewrite is that we introduce additional nesting, thus the different contexts.

In our case instead of a flat array of function pointers, we’ll have an array of arrays of function pointers. Unlike the solution you attempted though these are really the &foo<X, Y> function pointers we care about—and flattening is straightforward.

#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>, "" );
}

Coliru demo.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • Thank you for your answer. However, I struggle to understand the declaration just before the `main()`. It looks like an explicit instantiation, because if I remove it, the compiler complains about the corresponding missing symbol. Also, I don't understand how the compiler can see `size` of `dispatch` in `value[size]`? – Alexandre Hamez Sep 04 '14 at 09:43
  • 1
    @AlexandreHamez It is not an instantiation, but a definition. [This is what it looks like](http://coliru.stacked-crooked.com/a/c7b51e3b7be6a62e) for a non-template class. Because `dispatch` is a class template, we have to add a bit of boilerplate in our case. Such a definition is never required (for a `constexpr` static data member), but the compiler can still ask for it. Mine doesn’t request it for instance. – Luc Danton Sep 04 '14 at 13:40
1

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

To calculate the "product" of the two, I think you have to use helper classes to make it work. See this other question like yours: How to create the Cartesian product of a type list?

Community
  • 1
  • 1
Jared Grubb
  • 1,139
  • 9
  • 17