Skip to main content
13 of 36
edited title
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.
  • integer_pack_fold<BinaryFunctor, integer_pack<>, initial_value>, simulates C++17's fold expressions by using a constexpr functor 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
user2296177
  • 3.6k
  • 1
  • 16
  • 37