Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

split variadic template arguments

Tags:

How do I split variadic template arguments in two halves? Something like:

template <int d> struct a {   std::array <int, d> p, q;   template <typename ... T> a (T ... t) : p ({half of t...}), q ({other half of t...}) {}  }; 
like image 591
Thomas Avatar asked Mar 30 '11 10:03

Thomas


2 Answers

Luc's solution is clean and straightforward, but sorely lacks fun.
Because there is only one proper way to use variadic templates and it is to abuse them to do crazy overcomplicated metaprogramming stuff :)

Like this :

template <class T, size_t... Indx, class... Ts> std::array<T, sizeof...(Indx)> split_array_range_imp(pack_indices<Indx...> pi, Ts... ts) {     return std::array<T, sizeof...(Indx)>{get<Indx>(ts...)...}; //TADA }   template <class T, size_t begin, size_t end, class... Ts> std::array<T, end - begin> split_array_range(Ts... ts) {     typename make_pack_indices<end, begin>::type indices;     return split_array_range_imp<T>(indices, ts...); }  template <size_t N> struct DoubleArray {   std::array <int, N> p, q;    template <typename ... Ts>   DoubleArray (Ts ... ts) :   p( split_array_range<int, 0                , sizeof...(Ts) / 2 >(ts...) ),   q( split_array_range<int, sizeof...(Ts) / 2, sizeof...(Ts)     >(ts...) )   {   } };  int main() {     DoubleArray<3> mya{1, 2, 3, 4, 5, 6};     std::cout << mya.p[0] << "\n" << mya.p[1] << "\n" << mya.p[2] << std::endl;     std::cout << mya.q[0] << "\n" << mya.q[1] << "\n" << mya.q[2] << std::endl; } 

It is quite short, except that we need to code some helper :

First we need the structure make_pack_indices, which is used to generate a range of integer at compile-time. For example make_pack_indices<5, 0>::type is actually the type pack_indices<0, 1, 2, 3, 4>

template <size_t...> struct pack_indices {};  template <size_t Sp, class IntPack, size_t Ep> struct make_indices_imp;  template <size_t Sp, size_t ... Indices, size_t Ep> struct make_indices_imp<Sp, pack_indices<Indices...>, Ep> {     typedef typename make_indices_imp<Sp+1, pack_indices<Indices..., Sp>, Ep>::type type; };  template <size_t Ep, size_t ... Indices> struct make_indices_imp<Ep, pack_indices<Indices...>, Ep> {     typedef pack_indices<Indices...> type; };  template <size_t Ep, size_t Sp = 0> struct make_pack_indices {     static_assert(Sp <= Ep, "__make_tuple_indices input error");     typedef typename make_indices_imp<Sp, pack_indices<>, Ep>::type type; }; 

We also need a get() function, very similar to std::get for tuple, such as std::get<N>(ts...) return the Nth element of a parameters pack.

template <class R, size_t Ip, size_t Ij, class... Tp> struct Get_impl {     static R& dispatch(Tp...); };  template<class R,  size_t Ip, size_t Jp, class Head, class... Tp> struct Get_impl<R, Ip, Jp, Head, Tp...> {     static R& dispatch(Head& h, Tp&... tps)     {         return Get_impl<R, Ip, Jp + 1, Tp...>::dispatch(tps...);     } };  template<size_t Ip, class Head, class... Tp> struct Get_impl<Head, Ip, Ip, Head, Tp...> {     static Head& dispatch(Head& h, Tp&... tps)     {         return h;     } };   template <size_t Ip, class ... Tp> typename pack_element<Ip, Tp...>::type& get(Tp&... tps) {     return Get_impl<typename pack_element<Ip, Tp...>::type, Ip, 0, Tp...>::dispatch(tps...); } 

But to build get() we also need a pack_element helper structure, again very similar to std::tuple_element, such as pack_element<N, Ts...>::type is the Nth type of the parameters pack.

template <size_t _Ip, class _Tp> class pack_element_imp;  template <class ..._Tp> struct pack_types {};  template <size_t Ip> class pack_element_imp<Ip, pack_types<> > { public:     static_assert(Ip == 0, "tuple_element index out of range");     static_assert(Ip != 0, "tuple_element index out of range"); };  template <class Hp, class ...Tp> class pack_element_imp<0, pack_types<Hp, Tp...> > { public:     typedef Hp type; };  template <size_t Ip, class Hp, class ...Tp> class pack_element_imp<Ip, pack_types<Hp, Tp...> > { public:     typedef typename pack_element_imp<Ip-1, pack_types<Tp...> >::type type; };  template <size_t Ip, class ...Tp> class pack_element { public:     typedef typename pack_element_imp<Ip, pack_types<Tp...> >::type type; }; 

And here we go.
Actually I don't really understand why pack_element and get() are not in the standard library already. Those helpers are present for std::tuple, why not for parameters pack ?

Note : My implementation of pack_element and make_pack_indices is a direct transposition of std::tuple_element and __make_tuple_indices implementation found in libc++.

like image 160
Thomas Petit Avatar answered Sep 22 '22 17:09

Thomas Petit


We still lack a lot of helpers to manipulate variadic parameter packs (or I am not aware of them). Until a nice Boost library brings them to us, we can still write our own.

For example, if you are willing to postpone your array's initialization to the constructor body, you can create and use a function that copies part of the parameter pack to an output iterator:

#include <array> #include <cassert> #include <iostream>  // Copy n values from the parameter pack to an output iterator template < typename OutputIterator > void copy_n( size_t n, OutputIterator ) {   assert ( n == 0 ); }  template < typename OutputIterator, typename T, typename... Args > void copy_n( size_t n, OutputIterator out, const T & value, Args... args ) {   if ( n > 0 )   {     *out = value;     copy_n( n - 1, ++out, args... );   } }  // Copy n values from the parameter pack to an output iterator, starting at // the "beginth" element template < typename OutputIterator > void copy_range( size_t begin, size_t size, OutputIterator out ) {   assert( size == 0 ); }   template < typename OutputIterator, typename T, typename... Args > void copy_range( size_t begin, size_t size, OutputIterator out, T value, Args... args ) {   if ( begin == 0 )   {     copy_n( size, out, value, args... );   }   else   {     copy_range( begin - 1, size, out, args... );   } }   template < int N >  struct DoubleArray {   std::array< int, N > p;   std::array< int, N > q;    template < typename... Args >   DoubleArray ( Args... args )   {     copy_range( 0, N, p.begin(), args... );     copy_range( N, N, q.begin(), args... );   }   };  int main() {   DoubleArray<3> mya(1, 2, 3, 4, 5, 6);   std::cout << mya.p[0] << mya.p[2] << std::endl;  // 13   std::cout << mya.q[0] << mya.q[2] << std::endl;  // 46 } 

As you can see, you can (not so) easily create your own algorithms to manipulate parameter packs; all is needed is a good understanding of recursion and pattern matching (as always when doing Template MetaProgramming).

like image 41
Luc Touraille Avatar answered Sep 19 '22 17:09

Luc Touraille