Разверните пакеты параметров различной длины


Я хотел бы "сгенерировать" таблицу переходов указателей функций. Функции, на которые указывают, имеют два типа шаблонов. Для каждой возможной пары в двух списках типов должна быть создана отдельная функция. В идеале, мы могли бы иметь что-то вроде:

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

Как и ожидалось, он терпит неудачу, когда кортежи имеют разную длину:

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.

Чтобы достичь такого рода функциональности, я уже пытался и преуспел скосвенным (таблица первого перехода, которая содержит указатели на функции с другой таблицей переходов), но я нахожу его неуклюжим.

Итак, мой вопрос: есть ли обходной путь для этого?
4 5

4 ответа:

Ваш пример кода неверен, даже в том случае, если он компилируется (т. е. когда sizeof...(Xs) = = sizeof...(Кер-Ис)). Скажем, у вас есть N-ary кортежей, тогда jump_table имеет N*N элементов, но только первые N элементов инициализируются функцией PTR.

Во-первых, вам нужно внутренне соединить 2 списка типов:

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

Например,

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

Дает вам

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]>

Вернемся к вашему примеру:

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

Это должно сделать то, что вы ожидали.

Другие ответы здесь кажутся слишком сложными для рассматриваемой проблемы. Вот как бы я это сделал:

#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]();
}

Это принимается Clang 3.5 в его режиме C++14.

Мое нормальное решение для расширения продукта context( f<Xs, Ys>... ) /* not what we want */ состоит в том, чтобы переписать его в context2( g<Xs, Ys...>... ). Это означает, что g отвечает за расширение Ys относительно некоторого X, а конечное расширение выполняет g для всех Xs. Следствием такого переписывания является то, что мы вводим дополнительную вложенность, таким образом, различные контексты.

В нашем случае вместо плоского массива указателей функций мы получим массив массивов указателей функций. В отличие от решения, которое вы пытались, хотя эти на самом деле это указатели функций &foo<X, Y>, о которых мы заботимся, и выравнивание является простым.
#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 .

То, что у вас есть, на самом деле больше похоже на " zip " из двух списков (<X1,Y1>, <X2,Y2>, ...), который не работает, когда списки имеют разную длину.

Чтобы вычислить "продукт" из этих двух, я думаю, что вы должны использовать вспомогательные классы, чтобы заставить его работать. Смотрите этот другой вопрос, подобный вашему: Как создать декартово произведение списка типов?