Skip to main content
22 of 36
added integer_pack_union
user2296177
  • 3.6k
  • 1
  • 16
  • 37

Integer packs (and integer pack utilities) for template meta-programming

##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-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 type T.
  • index_pack<std::size_t...>, an alias of integer_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 an integer_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 an integer_pack<> with values in the range \$[ from, to ]\$ using an increment of step.
  • integer_pack_concatenate<L, R>, concatenates the right pack to the left pack.

##Usage/Tests

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.

#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>,
            "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 the src index to the position of the element at the dst index; integers wrap around as needed.
  • integer_pack_sort<T>, sorts the template argument integer pack by ascending order.
  • integer_pack_union<L, R>, performs the set union of two integer packs; the output set is sorted.

###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>);

    // union
    using u0 = integer_pack_union_t<integer_pack<int, 1, 5, -3>, integer_pack<int, -3, 5>>;
    static_assert(std::is_same_v<u0, integer_pack<int, -3, 1, 5>>);
}

###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
    {
        template<class T, std::size_t n, std::size_t... is>
        constexpr auto to_std_array(T const(&arr)[n], index_pack<is...>) noexcept
        {
            return std::array<T, n>{ arr[is]... };
        }

        template<class T, std::size_t n>
        constexpr auto to_std_array(T const(&arr)[n]) noexcept
        {
            return to_std_array(arr, make_index_sequence<n>{});
        }

        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>
        constexpr auto counting_sort(integer_pack<T, ints...>) 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++] = has_negative ? static_cast<T>(i) + min_value : i;

            return to_std_array(sorted_integers);
        }
    }

    template<class IntegerPack>
    struct integer_pack_sort
    {
    private:
        using backing_type = typename IntegerPack::backing_type;

        static constexpr auto sorted_integers{ impl::counting_sort(IntegerPack{}) };

        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;
}

// integer_pack_union
namespace ct
{
    namespace impl
    {
        template<class T, T... ints>
        constexpr auto make_found_pair(integer_pack<T, ints...>) noexcept
        {
            constexpr auto min{ find_integer(smallest{}, integer_pack<T, ints...>{}) };
            constexpr auto max{ find_integer(largest{}, integer_pack<T, ints...>{}) };

            bool found[(min < 0 ? max - min : max) + 1] = {};
    
            constexpr T integers[] = { ints... };
            std::size_t count{ 0 };
            for (std::size_t i{ 0 }, value{ 0 }; i < sizeof...(ints); ++i)
            {
                value = min < 0 ? integers[i] - min : integers[i];
                if (found[value])
                {
                    continue;
                }
                found[value] = true;
                ++count;
            }

            return std::make_pair(to_std_array(found), count);
        }

        template<class T, class U, T... l_ints, U... r_ints>
        constexpr auto integer_pack_union(
            integer_pack<T, l_ints...>, integer_pack<U, r_ints...>) noexcept
        {
            using common_t = std::common_type_t<T, U>;

            constexpr auto min{ find_integer(smallest{}, integer_pack<common_t, l_ints..., r_ints...>{}) };
            constexpr auto found{ make_found_pair(integer_pack<common_t, l_ints..., r_ints...>{}) };
            common_t union_integers[found.second] = {};
    
            for (std::size_t i{ 0 }, k{ 0 }; i < found.first.size(); ++i)
                if (found.first[i])
                    union_integers[k++] = min < 0 ? static_cast<common_t>(i) + min : i;

            return to_std_array(union_integers);
        }

        template<class T, std::size_t n>
        constexpr auto std_array_size(std::array<T, n> const&) noexcept
        {
            return n;
        }
    }

    template<class LIntegerPack, class RIntegerPack>
    struct integer_pack_union
    {
    private:
        using backing_type = std::common_type_t
        <
            typename LIntegerPack::backing_type,
            typename RIntegerPack::backing_type
        >;

        static constexpr auto union_integers = impl::integer_pack_union(
            LIntegerPack{}, RIntegerPack{});

        template<std::size_t... is>
        static constexpr auto make_union_integer_pack(index_pack<is...>) noexcept
        {
            return integer_pack<backing_type, union_integers[is]...>{};
        }

    public:
        using type = decltype(make_union_integer_pack(
            make_index_sequence<impl::std_array_size(union_integers)>{}));
    };

    template<class LIntegerPack, class RIntegerPack>
    using integer_pack_union_t = typename integer_pack_union
    <
        LIntegerPack, RIntegerPack
    >::type;
}
user2296177
  • 3.6k
  • 1
  • 16
  • 37