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.
###Major functionality 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...>.make_integer_sequence<T, T n>, make aninteger_pack<>type with values being in the range \$[ 0, n - 1 )\$.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_extract<index_pack<>, integer_pack<>>, creates an integer pack containing the integers at the indices of the argument index pack from the argument integer pack.integer_pack_fold<BinaryFunctor, integer_pack<>, initial_value>, simulates C++17's fold expressions by using aconstexprfunctor to collapse an integer pack into one value.
##Sample usage
#include "integer_pack.h"
struct mult
{
template<class T, class U>
constexpr auto operator()( T const a, U const b ) -> decltype( a * b )
{
return a * b;
}
};
int main()
{
// make an increasing integer range
using inc_integer_range_t = ct::make_integer_range<int, -2, 2, 1>;
using expected_inc_range_t = ct::integer_pack<int, -2, -1, 0, 1, 2>;
static_assert( std::is_same<expected_inc_range_t, inc_integer_range_t>::value, "!" );
// make a decreasing integer range
using dec_integer_range_t = ct::make_integer_range<int, 2, -2, 1>;
using expected_dec_range_t = ct::integer_pack<int, 2, 1, 0, -1, -2>;
static_assert( std::is_same<expected_dec_range_t, dec_integer_range_t>::value, "!" );
// collapse an integer pack by multiplying its values together
using int_pack_t = ct::make_integer_range<int, 1, 5>; // 5! = 1 * 2 * 3 * 4 * 5
static_assert( ct::integer_pack_fold<mult, int_pack_t, 1>::value == 120, "!" );
}
##Review goals
- Improving implementation in terms of compilation time or in terms of code size without affecting compilation time.
- Currently,
integer_pack_fold<>supports a maximum integer pack size of 10'000 on my compiler. I'd like to increase this limit without having to explicitly give more static memory to the compiler. - Suggestions on other useful operations that haven't been implemented and general style, efficiency, etc.
##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...
#ifndef CT_INTEGER_PACK_H
#define CT_INTEGER_PACK_H
#include <cstddef>
#include <array>
#include <type_traits>
// 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>,
"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;
}
// 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,
"unreachable integer range; invalid 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, integer_pack_extract
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... is, class T, T... ints>
constexpr auto extract(index_pack<is...>, integer_pack<T, ints...>) noexcept
{
constexpr T values[] = { ints... };
return integer_pack<T, values[is]...>{};
}
}
template<std::size_t i, class IntegerPack>
struct integer_pack_at : std::integral_constant<typename IntegerPack::backing_type, impl::at(i, IntegerPack{})>
{};
template<std::size_t i, class IntegerPack>
constexpr auto integer_pack_at_v = integer_pack_at<i, IntegerPack>::value;
template<class ExtractionIndexPack, class IntegerPack>
struct integer_pack_extract : decltype(impl::extract(ExtractionIndexPack{}, IntegerPack{}))
{};
template<class ExtractionIndexPack, class IntegerPack>
using integer_pack_extract_t = typename integer_pack_extract<ExtractionIndexPack, IntegerPack>::type;
}
// 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;
}
// integer_pack_fold
namespace ct
{
template<class BinaryFunctor, class T, std::size_t n, std::size_t... is>
constexpr auto fold( T ( &arr )[ n ],
index_pack<is...>, BinaryFunctor binary_functor = {} )
{
std::remove_const_t<T> fold_arr[] = { arr[ is ]... };
for ( std::size_t sz{ n }; sz != 2; )
{
if ( sz % 2 == 0 )
{
sz /= 2;
for ( std::size_t i{ 0 }, j{ 0 }; i != sz; ++i, j += 2 )
{
fold_arr[ i ] = binary_functor( fold_arr[ j ], fold_arr[ j + 1 ] );
}
}
else
{
std::size_t next_sz{ sz / 2 };
for ( std::size_t i{ 0 }, j{ 0 }; i != next_sz; ++i, j += 2 )
{
fold_arr[ i ] = binary_functor( fold_arr[ j ], fold_arr[ j + 1 ] );
}
fold_arr[ next_sz ] = fold_arr[ sz - 1 ];
sz = next_sz + 1;
}
}
return binary_functor( fold_arr[ 0 ], fold_arr[ 1 ] );
}
template<class BinaryFunctor, class IntegerPack>
struct integer_pack_fold;
template<class BinaryFunctor, class T, T... ints>
struct integer_pack_fold<BinaryFunctor, integer_pack<T, ints...>>
{
static constexpr T values[] = { ints... };
static constexpr T value
{
fold<BinaryFunctor>( values, make_index_sequence<sizeof...( ints )>{} )
};
};
}
#endif // CT_INTEGER_PACK_H