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?
With the variadic templates feature, you can define class or function templates that have any number (including zero) of parameters. To achieve this goal, this feature introduces a kind of parameter called parameter pack to represent a list of zero or more parameters for templates.
A variadic template is a class or function template that supports an arbitrary number of arguments. This mechanism is especially useful to C++ library developers: You can apply it to both class templates and function templates, and thereby provide a wide range of type-safe and non-trivial functionality and flexibility.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With