Разверните пакеты параметров различной длины
Я хотел бы "сгенерировать" таблицу переходов указателей функций. Функции, на которые указывают, имеют два типа шаблонов. Для каждой возможной пары в двух списках типов должна быть создана отдельная функция. В идеале, мы могли бы иметь что-то вроде:
#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 ответа:
Ваш пример кода неверен, даже в том случае, если он компилируется (т. е. когда 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>, "" ); }
То, что у вас есть, на самом деле больше похоже на " zip " из двух списков (
<X1,Y1>
,<X2,Y2>
, ...), который не работает, когда списки имеют разную длину.Чтобы вычислить "продукт" из этих двух, я думаю, что вы должны использовать вспомогательные классы, чтобы заставить его работать. Смотрите этот другой вопрос, подобный вашему: Как создать декартово произведение списка типов?