Bryce
Bryce

Reputation: 2207

Variadic templates: producing a tuple of pairs of adjacent elements

My goal is to do something so that for instance,

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

Has return type

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

I am wondering if this is even possible with C++ template metaprogramming, and how it could be accomplished. For actually producing the value, it seems as though I can use tuple_cat to recursively concatenate to the output, but I'm finding it difficult to express the return type, since it is itself variadic and effectively a function of the number of template parameters. Complicating the situation, if I went the tuple_cat route it seems like I would also have to overload the function to take a tuple to concatenate onto, and the concatenation would happen at runtime, not compile-time. Am I on a wild goose chase here?

Upvotes: 9

Views: 1336

Answers (3)

Synxis
Synxis

Reputation: 9388

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

First, let's define our result structures:

template<int a, int b>
struct tpair
{
};

template<typename... p>
struct final_
{
};

The key point is to concat parameters packs. Here is the struct that will do the job:

template<typename a, typename b>
struct concat
{
};

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

Now, the struct used to 'pairize' your list. Normally it will fail with odd numbers 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 the live example there is also a code outputting the name of all types in a parameter pack to the console, with demangling, as a test (was funnier to use than the incomplete type trick).

Upvotes: 5

Andy Prowl
Andy Prowl

Reputation: 126422

Here is a way of doing that. Given your class template some_other_type:

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

And given some machinery 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 could provide a simple function that instantiates the helper class template:

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

And here is how you would use it (and a 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 live example.


IMPROVEMENT: (why writing <1, 2, 3, 4> when one could write <1, 5>)?

It is also possible to extend the above solution so that it won't be required to manually write every number between the minimum and the maximum as a template argument of pairs(). Given the additional machinery below, again hidden in a 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...>();
    }
}

It is possible to define a helper function pairs_range() which accepts 2 template arguments defining the range [begin, end) - where end is not included, in the style of the Standard Library:

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 this is how one would use it (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 once again, here is a live example.

Upvotes: 12

Daniel Frey
Daniel Frey

Reputation: 56863

And now, let's try it with indices and without recursion (except, of course, for the indices):

#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 bit: std::tuple_cat might be recursive itself ;)


Update: OK, I should have read the question more carefully. Here's the version which produces 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" );
}

Upvotes: 3

Related Questions