##Introduction
In template meta-programming, integer sequences and ranges are very useful. I've made a couple of utility classes that do various operations on compile-time integer packs.
The implementation is non-linearly-recursive, allowing faster compilation and a greater number of template arguments.
#Part 1
Part 1 consists of the basic operations: creating sequences and ranges, getting the size of a pack, negating a pack of integers, indexing into a pack, and concatenation.
###Overview
integer_pack<T, T...>, a pack of integers of typeT.index_pack<std::size_t...>, an alias ofinteger_pack<std::size_t, std::size_t...>.integer_pack_size<T>, returns the size (number of integers in) an integer pack.integer_pack_negate_t<T>, negates the integers of an integer pack.make_integer_sequence<T, T n>, make aninteger_pack<>type with values being in the range \$[ 0, n - 1 )\$ when \$n >= 0\$ or \$[ 0, n + 1 )\$ when \$n < 0\$.make_integer_range<T, T from, T to, T step>, make aninteger_pack<>with values in the range \$[ from, to ]\$ using an increment ofstep.integer_pack_concatenate<L, R>, concatenates the left pack to the right pack.
##Usage/Tests
#include "integer_pack.h"
int main()
{
using namespace ct;
static_assert(integer_pack_size_v<integer_pack<int, -1, -3, 5, -6>> == 4);
using negated_t = integer_pack_negate_t<integer_pack<short, -1, -3, 5, -6>>;
static_assert(std::is_same_v<negated_t, integer_pack<short, 1, 3, -5, 6>>);
using seq_to5 = make_integer_sequence<int, 5>;
static_assert(std::is_same_v<seq_to5, integer_pack<int, 0, 1, 2, 3, 4>>);
using seq_to_minus5 = make_integer_sequence<int, -5>;
static_assert(std::is_same_v<seq_to_minus5, integer_pack<int, 0, -1, -2, -3, -4>>);
using range_minus2_3 = make_integer_range<int, -2, 3>;
static_assert(std::is_same_v<range_minus2_3, integer_pack<int, -2, -1, 0, 1, 2, 3>>);
using range_3_minus2 = make_integer_range<int, 3, -2>;
static_assert(std::is_same_v<range_3_minus2, integer_pack<int, 3, 2, 1, 0, -1, -2>>);
// increasing range, step of 2
using range_multiplesof2 = make_index_range<0, 8, 2>;
static_assert(std::is_same_v<range_multiplesof2, index_pack<0, 2, 4, 6, 8>>);
// decreasing range, step of 3
using range_3_to_minus9 = make_integer_range<char, 3, -9, 3>;
static_assert(std::is_same_v<range_3_to_minus9, integer_pack<char, 3, 0, -3, -6, -9>>);
static_assert(integer_pack_at_v<3, integer_pack<int, -1, -3, 5, -6>> == -6);
using c_t = integer_pack_concatenate_t<integer_pack<int, 1>, integer_pack<long, 2>>;
static_assert(std::is_same_v<c_t, integer_pack<long, 1, 2>>);
}
##Implementation
At the top of every namespace, there's a comment indicating which feature is inside. Now brace yourself for lots of template meta-programming fun...
Note: Include guard omitted.
// integer_pack, index_pack
namespace ct
{
template<class T, T... ints>
struct integer_pack : std::integral_constant<std::size_t, sizeof...(ints)>
{
static_assert(std::is_integral_v<T>,
"integer_pack: first template argument must be an integer type");
using backing_type = T;
using type = integer_pack<T, ints...>;
};
template<std::size_t... ints>
using index_pack = integer_pack<std::size_t, ints...>;
}
// integer_pack_size, integer_pack_size_v
namespace ct
{
template<class IntegerPack>
struct integer_pack_size;
template<class T, T... ints>
struct integer_pack_size<integer_pack<T, ints...>>
: std::integral_constant<std::size_t, sizeof...(ints)>
{};
template<class IntegerPack>
constexpr auto integer_pack_size_v = integer_pack_size<IntegerPack>::value;
}
// integer_pack_negate, make_integer_sequence, make_index_sequence
namespace ct
{
template<class IntegerPack>
struct integer_pack_negate;
template<class T, T... ints>
struct integer_pack_negate<integer_pack<T, ints...>>
: integer_pack<T, (-ints)...>
{};
template<class IntegerPack>
using integer_pack_negate_t = typename integer_pack_negate<IntegerPack>::type;
namespace impl
{
template<class T, class LIntegerPack, class RIntegerPack>
struct integer_pack_merge;
template<class T, T... l_ints, T... r_ints>
struct integer_pack_merge
<
T, integer_pack<T, l_ints...>, integer_pack<T, r_ints...>
> : integer_pack<T, l_ints..., sizeof...(l_ints) + r_ints...>
{};
template<class T, T n, class = void>
struct integer_sequence_generate
: integer_pack_merge
<
T,
typename integer_sequence_generate<T, n / 2>::type,
typename integer_sequence_generate<T, n / 2 + n % 2>::type
>
{};
template<class T, T n>
struct integer_sequence_generate<T, n, std::enable_if_t<(n == 1)>>
: integer_pack<T, n - 1>
{};
template<class T, T n>
struct integer_sequence_generate<T, n, std::enable_if_t<(n == 0)>>
: integer_pack<T>
{};
template<class T, T n>
struct integer_sequence_generate<T, n, std::enable_if_t<(n < 0)>>
{
using type = typename integer_pack_negate
<
typename integer_sequence_generate<T, -n>::type
>::type;
};
}
template<class T, T n>
using make_integer_sequence = typename impl::integer_sequence_generate<T, n>::type;
template<std::size_t n>
using make_index_sequence = make_integer_sequence<std::size_t, n>;
}
// make_integer_range, make_index_range
namespace ct
{
namespace impl
{
template
<
class T,
T from,
T to,
T step,
T n_vals = (from < to ? to - from : from - to)
>
struct integer_range_generate
{
private:
static_assert(n_vals % step == 0,
"integer_range_generate: unreachable integer range; bad step value");
template<class IntegerPack, bool is_increasing>
struct integer_range_generate_impl;
template<T... ints>
struct integer_range_generate_impl<integer_pack<T, ints...>, true>
: integer_pack<T, (from + step * ints)...>
{};
template<T... ints>
struct integer_range_generate_impl<integer_pack<T, ints...>, false>
: integer_pack<T, (from - step * ints)...>
{};
public:
using type = typename integer_range_generate_impl
<
make_integer_sequence<T, 1 + n_vals / step>, (from < to)
>::type;
};
template<class T, T n, T step, T n_vals>
struct integer_range_generate<T, n, n, step, n_vals>
: integer_pack<T, n>
{};
}
template<class T, T from, T to, T step = 1>
using make_integer_range = typename impl::integer_range_generate
<
T, from, to, step
>::type;
template<std::size_t from, std::size_t to, std::size_t step = 1>
using make_index_range = make_integer_range<std::size_t, from, to, step>;
}
// integer_pack_at
namespace ct
{
namespace impl
{
template<class T, T... ints>
constexpr auto at(std::size_t const i, integer_pack<T, ints...>) noexcept
{
constexpr T values[] = { ints... };
return values[i];
}
}
template<std::size_t i, class IntegerPack>
struct integer_pack_at
: std::integral_constant
<
typename IntegerPack::backing_type, impl::at(i, IntegerPack{})
>
{
static_assert(integer_pack_size<IntegerPack>::value != 0,
"integer_pack_at: empty integer pack");
static_assert(i < integer_pack_size<IntegerPack>::value,
"integer_pack_at: index out of bounds");
};
template<std::size_t i, class IntegerPack>
constexpr auto integer_pack_at_v = integer_pack_at<i, IntegerPack>::value;
}
// integer_pack_concatenate
namespace ct
{
template<class LIntegerPack, class RIntegerPack>
struct integer_pack_concatenate;
template<class T, class U, T... l_ints, U... r_ints>
struct integer_pack_concatenate
<
integer_pack<T, l_ints...>, integer_pack<U, r_ints...>
>
{
using type = integer_pack<std::common_type_t<T, U>, l_ints..., r_ints...>;
};
template<class LIntegerPack, class RIntegerPack>
using integer_pack_concatenate_t = typename integer_pack_concatenate
<
LIntegerPack, RIntegerPack
>::type;
}
#Part 2
Part 2 consists of manipulating an integer pack: shifting the pack left or right (where values wrap around), and sorting a pack.
###Overview
integer_pack_shift<src, dst, T>, shifts the elements starting from the element at thesrcindex to the position of the element at thedstindex; integers wrap around as needed.integer_pack_sort<T>, sorts the template argument integer pack by ascending order.
###Usage/tests
int main()
{
using namespace ct;
// left to right shift
using pack_t = integer_pack<int, 1, 3, 5, 7, 11>;
using l_expected_t = integer_pack<int, 7, 11, 1, 3, 5>;
static_assert(std::is_same_v<integer_pack_shift_t<1, 3, pack_t>, l_expected_t>);
// right to left shift
using r_expected_t = integer_pack<int, 5, 7, 11, 1, 3>;
static_assert(std::is_same_v<integer_pack_shift_t<2, 0, pack_t>, r_expected_t>);
// more shifting
using shift_t = integer_pack<long, -3, -5, 1, -7, 11>;
using expected_shift_t = integer_pack<long, -5, 1, -7, 11, -3>;
static_assert(std::is_same_v<integer_pack_shift_t<0, 4, shift_t>, expected_shift_t>);
// sorting
using l_sorted_t = integer_pack_sort_t<l_expected_t>;
using r_sorted_t = integer_pack_sort_t<r_expected_t>;
static_assert(std::is_same_v<l_sorted_t, r_sorted_t>);
// more sorting
using to_sort_t = integer_pack<long, 5, 3, 17, -15, 8, -3, -23, 1>;
using expected_sorted_t = integer_pack<long, -23, -15, -3, 1, 3, 5, 8, 17>;
static_assert(std::is_same_v<integer_pack_sort_t<to_sort_t>, expected_sorted_t>);
}
###Implementation
Note: Include guard omitted.
// index_sequence_shift, integer_pack_shift
namespace ct
{
namespace impl
{
template
<
std::size_t src,
std::size_t dst,
class IndexSequence,
bool is_right_to_left
>
struct index_sequence_shift_impl
{
using m_seq = make_index_range
<
src, integer_pack_size<IndexSequence>::value - 1
>;
using r_seq = make_index_sequence<src - dst>;
using l_seq = std::conditional_t
<
(dst == 0),
index_pack<>,
make_index_range<integer_pack_size<r_seq>::value, src - 1>
>;
};
template<std::size_t src, std::size_t dst, class IndexSequence>
struct index_sequence_shift_impl<src, dst, IndexSequence, false>
{
static constexpr auto last_offset
{
integer_pack_size<IndexSequence>::value - 1
};
using m_seq = make_index_sequence<src>;
using r_seq = make_index_range<src, src + (last_offset - dst)>;
using l_seq = make_index_range
<
integer_pack_at<integer_pack_size<r_seq>::value - 1, r_seq>::value + 1,
last_offset
>;
};
}
template<std::size_t src, std::size_t dst, class IndexSequence>
struct index_sequence_shift
{
private:
using shift_t = impl::index_sequence_shift_impl
<
src, dst, IndexSequence, (src > dst)
>;
using l_seq = typename shift_t::l_seq;
using m_seq = typename shift_t::m_seq;
using r_seq = typename shift_t::r_seq;
public:
using type = integer_pack_concatenate_t
<
l_seq, integer_pack_concatenate_t<m_seq, r_seq>
>;
};
template<std::size_t dst, class IndexSequence>
struct index_sequence_shift<dst, dst, IndexSequence>
{
using type = IndexSequence;
};
template<std::size_t src, std::size_t dst, class IndexSequence>
using index_sequence_shift_t = typename index_sequence_shift
<
src, dst, IndexSequence
>::type;
template<std::size_t src, std::size_t dst, class IntegerPack>
struct integer_pack_shift
{
private:
template<class IndexSequence>
struct integer_pack_shift_impl;
template<std::size_t... is>
struct integer_pack_shift_impl<index_pack<is...>>
{
using type = integer_pack
<
typename IntegerPack::backing_type,
integer_pack_at<is, IntegerPack>::value...
>;
};
public:
using type = typename integer_pack_shift_impl
<
index_sequence_shift_t
<
src,
dst,
make_index_sequence<integer_pack_size<IntegerPack>::value>
>
>::type;
};
template<std::size_t src, std::size_t dst, class IntegerPack>
using integer_pack_shift_t = typename integer_pack_shift
<
src, dst, IntegerPack
>::type;
}
// integer_pack_sort
namespace ct
{
namespace impl
{
struct smallest
{
template<class T>
constexpr auto operator()(T const& lhs, T const& rhs) noexcept
{
return lhs > rhs;
}
};
struct largest
{
template<class T>
constexpr auto operator()(T const& lhs, T const& rhs) noexcept
{
return lhs < rhs;
}
};
template<class Predicate, class T, T... ints>
constexpr auto find_integer(Predicate p, integer_pack<T, ints...>) noexcept
{
T integers[] = { ints... };
T value{ 0 };
for (std::size_t i{ 0 }; i < sizeof...(ints); ++i)
if (p(value, integers[i]))
value = integers[i];
return value;
}
template<bool increasing, class T, T offset, class IntegerPack>
struct integer_pack_sort_offset;
template<bool increasing, class T, T offset, T... ints>
struct integer_pack_sort_offset<increasing, T, offset, integer_pack<T, ints...>>
{
using type = std::conditional_t
<
increasing,
index_pack<(ints + offset)...>,
integer_pack<T, (ints - offset)...>
>;
};
template<bool increasing, std::size_t offset, class IntegerPack>
using integer_pack_sort_offset_t = typename integer_pack_sort_offset
<
increasing, typename IntegerPack::backing_type, offset, IntegerPack
>::type;
template<class T>
constexpr T abs_impl(T const value, std::true_type) noexcept
{
return value < 0 ? -value : value;
}
template<class T>
constexpr T abs_impl(T const value, std::false_type) noexcept
{
return value;
}
template<class T>
constexpr T abs(T const value) noexcept
{
return abs_impl(
value, std::integral_constant<bool, std::is_signed<T>::value>{});
}
template<class T, T... ints, std::size_t... is>
constexpr auto counting_sort(integer_pack<T, ints...>, index_pack<is...>)
noexcept
{
using pack_t = integer_pack<T, ints...>;
constexpr auto min_value{ find_integer(smallest{}, pack_t{}) };
constexpr auto has_negative{ min_value < 0 };
using offset_pack_t = std::conditional_t
<
has_negative,
integer_pack_sort_offset_t<true, abs(min_value), pack_t>,
pack_t
>;
constexpr auto max_value{ find_integer(largest{}, offset_pack_t{}) };
T integers[] = { (has_negative ? ints + abs(min_value) : ints)... };
std::size_t counts[max_value + 1] = {};
for (std::size_t i{ 0 }; i < sizeof...(ints); ++i)
++counts[integers[i]];
T sorted_integers[sizeof...(ints)] = {};
for (std::size_t i{ 0 }, k{ 0 }; i < max_value + 1; i++)
for (std::size_t j{ 0 }; j < counts[i]; j++)
sorted_integers[k++] = i;
return std::array<T, sizeof...(ints)>
{
(has_negative ? sorted_integers[is] + min_value : sorted_integers[is])...
};
}
}
template<class IntegerPack>
struct integer_pack_sort
{
private:
using backing_type = typename IntegerPack::backing_type;
static constexpr auto sorted_integers = impl::counting_sort(IntegerPack{},
make_index_sequence<integer_pack_size<IntegerPack>::value>{});
template<std::size_t... is>
static constexpr auto make_sorted_integer_pack(index_pack<is...>) noexcept
{
return integer_pack<backing_type, sorted_integers[is]...>{};
}
public:
using type = decltype(make_sorted_integer_pack(
make_index_sequence<integer_pack_size<IntegerPack>::value>{}));
};
template<class IntegerPack>
using integer_pack_sort_t = typename integer_pack_sort<IntegerPack>::type;
}