2014-09-04 2 views
5

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

#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. 

Для достижения такого рода функциональности, я уже пробовал и успешно с indirection (а первая таблица перехода, содержащая указатели на функции с другой таблицей перехода), но я нахожу ее неуклюжей.

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

ответ

4

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

Во-первых, вам необходимо внутреннее соединение в 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{}); 
} 

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

+0

Спасибо, это очень просто, а часть манипулирования списком легко использовать повторно. –

+0

Элемент списка можно сделать немного более гладким с помощью шаблона struct L {using type = L;}; 'и наследования, а также несколько шаблонов с использованием cat_t = typename cat :: type;' aliases. [См. Здесь] (http://ideone.com/hI4Ohu). – Yakk

1

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

Чтобы вычислить «продукт» двух, я думаю, вам нужно использовать вспомогательные классы, чтобы заставить его работать. См. Этот другой вопрос, как ваш: How to create the Cartesian product of a type list?

2

Мое нормальное решение для расширения продукта 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.

+0

Благодарим вас за ответ. Однако я стараюсь понять декларацию перед «main()». Это похоже на явное инстанцирование, потому что, если я его удалю, компилятор жалуется на соответствующий отсутствующий символ. Кроме того, я не понимаю, как компилятор может видеть 'size'' dispatch' в 'value [size]'? –

+1

@AlexandreHamez Это не экземпляр, а определение. [Это выглядит так) (http://coliru.stacked-crooked.com/a/c7b51e3b7be6a62e) для класса без шаблона. Поскольку 'dispatch' является шаблоном класса, мы должны добавить немного шаблона в нашем случае. Такое определение никогда не требуется (для статического элемента данных 'constexpr'), но компилятор все равно может его попросить. Мое не спрашивает об этом, например. –

3

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

#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.

+0

Я попробовал что-то подобное, прежде чем задавать этот вопрос, но он не компилируется с clang 3.4 или g ++ 4.9 ... Однако я согласен, это гораздо более простое решение. –

+0

Я возражаю против описания моего ответа как «слишком сложного», видя, что мы делаем то же самое. Я делаю это более подробно и через специализированные шаблоны классов, и это для дидактических целей - я стараюсь, чтобы другие могли попробовать код на Coliru, чтобы они могли понять мой ответ, даже когда наши компиляторы отличаются. Очевидно, что короткий ответ, состоящий в основном из кода, также имеет свои достоинства, но я считаю, что это неслучайное замечание. –

+1

Я не согласен с тем, что ваше решение имеет более образовательную ценность; он предоставляет больше методов и работает в большем количестве ситуаций. Но именно это делает его слишком сложным для проблемы. –

Смежные вопросы