Variad templates: creating a tuple of pairs of adjacent elements

My goal is to do something so that, for example,

pairs<1,2,3,4>() 

Returns type

 std::tuple<some_other_type<1,2>, some_other_type<2,3>, some_other_type<3,4>> 

I am wondering if this is possible using metaprogramming C ++ templates and how this can be done. For the actual creation of the value, it seems that I can use tuple_cat to recursively concatenate the output, but it is difficult for me to express the return type, since it is itself a variable and efficient function of the number of template parameters. Complicating the situation, if I went along the tuple_cat route, it seems to me that I will also have to overload the function in order to take the tuple in order to combine, and concatenation will occur at runtime, and not at compile time. Am I here on a wild goose chase?

+7
source share
3 answers

Here's how to do it. Given your template template some_other_type :

 template<int I, int J> struct some_other_type { }; 

And given some of the mechanisms hidden in the detail namespace:

 namespace detail { template<int... Is> struct pairs { }; template<int I, int J> struct pairs<I, J> { using type = std::tuple<some_other_type<I, J>>; }; template<int I, int J, int... Is> struct pairs<I, J, Is...> { using type = decltype(std::tuple_cat( std::tuple<some_other_type<I, J>>(), typename pairs<J, Is...>::type())); }; } 

You can provide a simple function that instantiates an auxiliary class template:

 template<int... Is> typename detail::pairs<Is...>::type pairs() { return typename detail::pairs<Is...>::type(); } 

And here is how you use it (and test case):

 #include <type_traits> int main() { auto p = pairs<1, 2, 3, 4>(); // Won't fire! static_assert( std::is_same< decltype(p), std::tuple< some_other_type<1,2>, some_other_type<2,3>, some_other_type<3,4>> >::value, "Error!"); } 

Finally, here is a living example .


IMPROVEMENT: (why write <1, 2, 3, 4> when you can write <1, 5> )?

It is also possible to extend the above solution so that you do not need to manually record each number between the minimum and maximum as an argument to the pairs() template. Given the additional technique below, again hidden in the detail namespace:

 namespace detail { template <int... Is> struct index_list { }; template <int MIN, int N, int... Is> struct range_builder; template <int MIN, int... Is> struct range_builder<MIN, MIN, Is...> { typedef index_list<Is...> type; }; template <int MIN, int N, int... Is> struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...> { }; // Meta-function that returns a [MIN, MAX) index range template<int MIN, int MAX> using index_range = typename range_builder<MIN, MAX>::type; template<int... Is> auto pairs_range(index_list<Is...>) -> decltype(::pairs<Is...>()) { return ::pairs<Is...>(); } } 

You can define an auxiliary function pairs_range() , which takes 2 template arguments defining the range [begin, end) -, where end not part of the standard library style:

 template<int I, int J> auto pairs_range() -> decltype(pairs_range(detail::index_range<I, J>())) { return pairs_range(detail::index_range<I, J>()); } 

And so it can be used (including a test case):

 int main() { // Won't fire! static_assert( std::is_same< decltype(pairs_range<1, 5>()), decltype(pairs<1, 2, 3, 4>()) >::value, "Error!"); } 

And again, here is a living example .

+12
source

Here is my version ( live here ), 100% of the compilation time, returning the new parameter list as a type (not a return function):

First we define our result structures:

 template<int a, int b> struct tpair { }; template<typename... p> struct final_ { }; 

The key point is the concat parameters package. Here is the structure that will complete the task:

 template<typename a, typename b> struct concat { }; template<typename a, typename... b> struct concat<a, final<b...>> { typedef final_<a,b...> type; }; 

Now the structure is used to pair your list. Usually it crashes with an odd number of values:

 template<int a, int b, int... values> struct pairize { // Choose one of the following versions: // First version: only non-overlapping pairs : (1,2) (3,4) ... typedef typename concat<tpair<a,b>, typename pairize<values...>::type>::type type; // Second version: overlapping pairs : (1,2) (2,3) (3,4)... typedef typename concat<tpair<a,b>, typename pairize<b,values...>::type>::type type; }; template<int a, int b> struct pairize<a,b> { typedef final_<tpair<a,b>> type; }; 

In a live example, there is also code that displays the name of all types in the parameter package to the console with dismantling as a test (it was more funny than an incomplete type trick) sub>.

+4
source

Now let's try it with indices and without recursion (except, of course, for indexes):

 #include <tuple> template< std::size_t... Ns > struct indices { typedef indices< Ns..., sizeof...( Ns ) > next; }; template< std::size_t N > struct make_indices { typedef typename make_indices< N - 1 >::type::next type; }; template<> struct make_indices< 0 > { typedef indices<> type; }; template< std::size_t, std::size_t > struct sometype {}; template< typename, typename, typename > struct make_pairs; template< std::size_t... Ns, std::size_t... Ms, std::size_t... Is > struct make_pairs< indices< Ns... >, indices< Ms... >, indices< Is... > > { using type = decltype( std::tuple_cat( std::declval< typename std::conditional< Is % 2 == 1, std::tuple< sometype< Ns, Ms > >, std::tuple<> >::type >()... )); }; template< std::size_t... Ns > using pairs = typename make_pairs< indices< 0, Ns... >, indices< Ns..., 0 >, typename make_indices< sizeof...( Ns ) + 1 >::type >::type; int main() { static_assert( std::is_same< pairs<1,2,4,3,5,9>, std::tuple< sometype<1,2>, sometype<4,3>, sometype<5,9> > >::value, "Oops" ); } 

(OK, I cheated a little: std::tuple_cat may be recursive;)


Update: Well, I should have carefully read the question. Here is the version that gives the desired result ( indices / make_indices as above):

 template< std::size_t, std::size_t > struct sometype {}; template< typename, typename, typename > struct make_pairs; template< std::size_t... Ns, std::size_t... Ms, std::size_t... Is > struct make_pairs< indices< Ns... >, indices< Ms... >, indices< Is... > > { using type = decltype( std::tuple_cat( std::declval< typename std::conditional< Is != 0 && Is != sizeof...( Is ) - 1, std::tuple< sometype< Ns, Ms > >, std::tuple<> >::type >()... )); }; template< std::size_t... Ns > using pairs = typename make_pairs< indices< 0, Ns... >, indices< Ns..., 0 >, typename make_indices< sizeof...( Ns ) + 1 >::type >::type; int main() { static_assert( std::is_same< pairs<1,2,3,4>, std::tuple< sometype<1,2>, sometype<2,3>, sometype<3,4> > >::value, "Oops" ); } 
+3
source

All Articles