Note: please check out std::integer_sequence
, its specialization index_sequence
and its make_index_sequence
and index_sequence_for
facilities.
If you want to declare them yourself, you generally would use inner types:
template <typename, typename> struct Concat;
template <unsigned... Is, unsigned... Js>
struct Concat<Sequence<Is...>,Sequence<Js...>> {
using type = Sequence<Is..., Js...>;
};
template <unsigned N>
struct Range {
using type = typename Concat<typename Range<N-1>::type, Sequence<N>>::type;
};
template <> struct Range<0> { using type = Sequence<0>; };
Which can of course be combined to template aliases for a shorter final form:
template <typename, typename> struct ConcatH;
template <unsigned... Is, unsigned... Js>
struct ConcatH<Sequence<Is...>,Sequence<Js...>> {
using type = Sequence<Is..., Js...>;
};
template <typename L, typename R>
using Concat = typename ConcatH<L, R>::type;
and:
template <unsigned N>
struct RangeH {
using type = Concat<typename RangeH<N-1>::type, Sequence<N>>;
};
template <> struct RangeH<0> { using type = Sequence<0>; };
template <unsigned N>
using Range = typename RangeH<N>::type;
However, indeed, this is slightly verbose. Still, there is little cruft here:
- baseline declaration
- specialization (to end recursion or limit to "interesting" types)
- alias declaration
on the other hand it's only written once.