Skip to main content
16 of 36
deleted 1211 characters in body
user2296177
  • 3.6k
  • 1
  • 16
  • 37

Integer packs and utilities for template meta-programming - Part 1

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 type T.
  • index_pack<std::size_t...>, an alias of integer_pack<std::size_t, std::size_t...>.
  • make_integer_sequence<T, T n>, make an integer_pack<> type with values being in the range \$[ 0, n - 1 )\$.
  • make_integer_range<T, T from, T to, T step>, make an integer_pack<> with values in the range \$[ from, to ]\$ using an increment of step.
  • 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.

##Sample usage

#include "integer_pack.h"

int main()
{
    using namespace ct;

    integer_pack_size_v<integer_pack<int, -1, -3, 5, -6>>;  // 4

    integer_pack_negate_t<integer_pack<int, -1, -3, 5, -6>>; // 1, 3, -5, 6

    make_integer_sequence<int, 5>;  // 0, 1, 2, 3, 4
    make_integer_sequence<int, -5>; // 0, -1, -2, -3, -4

    make_integer_range<int, -2, 3>;     // -2, -1, 0, 1, 2, 3
    make_integer_range<int, 3, -2>;     // 3, 2, 1, 0, -1, -2
    make_index_range<0, 8, 2>;          // 0, 2, 4, 6, 8
    make_integer_range<int, -9, 3, 3>;  // -9, -6, -3, 0, 3

    integer_pack_at_v<2, integer_pack<int, -1, -3, 5, -6>>; // 5
}

##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 <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>,
            "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; 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
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;
}

#endif // CT_INTEGER_PACK_H
user2296177
  • 3.6k
  • 1
  • 16
  • 37