18
\$\begingroup\$

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 (except for \$log(n)\$ recursions when generating a pack), allowing faster compilation and a greater number of template arguments.

Note 1: In every implementation section, at the top of every namespace, there's a comment indicating which feature is inside.

Note 2: Include guards are omitted.

Note 3: The following headers are used:

#include <cstddef>
#include <array>
#include <limits>
#include <type_traits>

Part 1

Part 1 consists of the basic operations:

  1. Integer packs themselves.
  2. Getting the size of an integer pack.
  3. Creating sequences and ranges.

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<IntegerPack>

Provides the number of integers in the specified template argument integer pack.

  • make_integer_sequence<T, T n>

Makes an integer_pack<T, T...> type where T... is the values in the range \$[ 0, n - 1 )\$ when \$n >= 0\$. The template performs \$log(n)\$ recursions to generate the sequence.

  • make_integer_range<T, T from, T to, T step>

Make an integer_pack<T, T...> type where T... is the values in the range \$[from, to]\$ using an increment/decrement of step. step will be subtracted if \$from > to\$.

Tests

// integer_pack_test_make_sequence_and_make_range
namespace ct
{
    namespace test
    {
        template<class Result, class Expected>
        constexpr void integer_pack_test() noexcept
        {
            static_assert(std::is_same<Result, Expected>::value);
        }

        constexpr void integer_pack_test_make_sequence_and_make_range() noexcept
        {
            // make sequence
            using make_seq5_t = make_integer_sequence<int, 5>;
            using expected_make_seq5_t = integer_pack<int, 0, 1, 2, 3, 4>;
            integer_pack_test<make_seq5_t, expected_make_seq5_t>();

            // make sequence 0 : make empty sequence
            using make_seq0_t = make_integer_sequence<int, 0>;
            using expected_make_seq0_t = integer_pack<int>;
            integer_pack_test<make_seq0_t, expected_make_seq0_t>();

            // make range
            using make_range_3_minus2 = make_integer_range<int, 3, -2>;
            using expected_make_range_3_minus2 = integer_pack<int, 3, 2, 1, 0, -1, -2>;
            integer_pack_test<make_range_3_minus2, expected_make_range_3_minus2>();

            // make increasing index range, step of 2
            using make_range_mult_of2 = make_index_range<0, 8, 2>;
            using expected_make_range_mult_of2 = index_pack<0, 2, 4, 6, 8>;
            integer_pack_test<make_range_mult_of2, expected_make_range_mult_of2>();

            // make decreasing integer range, step of 3
            using make_range_3_to_neg9 = make_integer_range<char, 3, -9, 3>;
            using expected_make_range_3_to_neg9 = integer_pack<char, 3, 0, -3, -6, -9>;
            integer_pack_test<make_range_3_to_neg9, expected_make_range_3_to_neg9>();

            // make integer range n to n, example: 0 to 0
            using make_range0_t = make_integer_range<char, 0, 0>;
            using expected_make_range0_t = integer_pack<char, 0>;
            integer_pack_test<make_range0_t, expected_make_range0_t>();
        }
    }
}

Running the tests:

int main()
{
    ct::test::integer_pack_test_make_sequence_and_make_range();
}

Implementation

// integer_pack, index_pack
namespace ct
{
    template<class T, T... ints>
    struct integer_pack
    {
        static_assert(std::is_integral<T>::value,
            "integer_pack: first template argument must be an integer type");

        using integer_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
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_sequence, make_index_sequence
namespace ct
{
    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, 0>
        {};

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

Part 2

Part 2 consists of the modifying operations:

  1. Extracting a range of values.
  2. Reversing.
  3. Indexing.
  4. Finding the minimum/maximum values.
  5. Concatenating 2 packs.
  6. Shifting the values inside a pack with wrap-around.
  7. Offsetting an integer pack.

Part 2 consists of manipulating an integer pack: shifting the pack left or right (where values wrap around).

Overview

  • integer_pack_extract<src, dst, IntegerPack>

Extracts from the specified template argument integer pack the values from at indices in the range \$[src, dst]\$. If \$dst < src\$, a reverse extraction is performed.

  • integer_pack_reverse<IntegerPack>

Reverses the order of the integers in the specified template argument integer pack.

  • integer_pack_at<i, IntegerPack>

Provides the integer at the specified offset i in the specified template argument integer pack.

  • integer_pack_minimum<IntegerPack>

Provides the smallest integer in the specified template argument integer pack.

  • integer_pack_maximum<IntegerPack>

Provides the largest integer in the specified template argument integer pack.

  • integer_pack_concatenate<LIntegerPack, RIntegerPack>

Concatenates the right pack to the left pack. The output set will have the integer type returned by applying std::common_type<T, U> to the integer types of the left and right integer packs.

  • 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:

If \$src < dst\$, integers will be shifted left to right.

If \$dst < src\$, integers will be shifted right to left.

If \$src == dst\$, the template is a no-op and aliases the specified template argument integer pack.

  • integer_pack_offset<offset, IntegerPack, bool increasing>

Offsets the integers in the specified template argument integer pack by the specified value of offset.

If the boolean template parameter is true, the offset is added to the integers.

If the boolean template parameter is false, the offset is subtracted from the integers.

Tests

// test_integer_pack_modifiers
namespace ct
{
    namespace test
    {
        constexpr void test_integer_pack_modifiers() noexcept
        {
            // extract
            using extract_t = integer_pack<int, -1, 2, -4>;
            using expected_extract_t = integer_pack<int, 2, -4>;
            using result_extract_t = integer_pack_extract_t<1, 2, extract_t>;
            integer_pack_test<result_extract_t, expected_extract_t>();

            // extract in reverse
            using rextract_t = integer_pack<int, -1, 2, -4>;
            using rexpected_extract_t = integer_pack<int, -4, 2>;
            using rresult_extract_t = integer_pack_extract_t<2, 1, rextract_t>;
            integer_pack_test<rresult_extract_t, rexpected_extract_t>();

            // extract n to n, example: 0 to 0
            using extract1_t = integer_pack<int, -1, 2, -4>;
            using expected_extract1_t = integer_pack<int, -1>;
            using result_extract1_t = integer_pack_extract_t<0, 0, extract1_t>;
            integer_pack_test<result_extract1_t, expected_extract1_t>();

            // reverse
            using reverse_t = integer_pack<int, 0, -1, -2, -3>;
            using expected_reverse_t = integer_pack<int, -3, -2, -1, 0>;
            using result_reverse_t = integer_pack_reverse_t<reverse_t>;
            integer_pack_test<result_reverse_t, expected_reverse_t>();

            // reverse empty integer pack
            using reverse_t0 = integer_pack<int>;
            using expected_reverse_t0 = integer_pack<int>;
            using result_reverse_t0 = integer_pack_reverse_t<reverse_t0>;
            integer_pack_test<result_reverse_t0, expected_reverse_t0>();

            // integer at
            using at_t = integer_pack<int, 3, -3>;
            constexpr auto at_t_value0{ integer_pack_at_v<0, at_t> };
            constexpr auto at_t_value1{ integer_pack_at_v<1, at_t> };
            static_assert(at_t_value0 == 3);
            static_assert(at_t_value1 == -3);

            // minimum
            constexpr auto expected_min_t_value{ -2 };
            using min_t = integer_pack<char, -1, expected_min_t_value, 2>;
            constexpr auto min_t_value{ integer_pack_minimum_v<min_t> };
            static_assert(min_t_value == expected_min_t_value);

            // maximum
            constexpr auto expected_max_t_value{ 3 };
            using max_t = integer_pack<char, -1, expected_max_t_value, 2>;
            constexpr auto max_t_value{ integer_pack_maximum_v<max_t> };
            static_assert(max_t_value == expected_max_t_value);

            // concatenate
            using l_concat_t = integer_pack<int, 0>;
            using r_concat_t = integer_pack<char, 1>;

            using expected_concat_t0 = integer_pack<int, 0, 1>;
            using result_concat_t0 = integer_pack_concatenate_t<l_concat_t, r_concat_t>;
            integer_pack_test<result_concat_t0, expected_concat_t0>();

            using result_concat_t1 = integer_pack_concatenate_t<r_concat_t, l_concat_t>;
            using expected_concat_t1 = integer_pack<int, 1, 0>;
            integer_pack_test<result_concat_t1, expected_concat_t1>();

            // increasing offset
            using ioffset_t = integer_pack<int, -2, -1, 0>;
            using expected_ioffset_t = integer_pack<int, 0, 1, 2>;
            using result_ioffset_t = integer_pack_offset_t<2, ioffset_t, true>;
            integer_pack_test<result_ioffset_t, expected_ioffset_t>();

            // decreasing offset
            using doffset_t = integer_pack<int, -2, -1, 0>;
            using expected_doffset_t = integer_pack<int, -4, -3, -2>;
            using result_doffset_t = integer_pack_offset<2, doffset_t, false>::type;
            integer_pack_test<result_doffset_t, expected_doffset_t>();

            // shift
            using pack_t = integer_pack<int, 1, 3, 5, 7, 11>;

            // left to right shift - src to dst
            using l_expected_t = integer_pack<int, 7, 11, 1, 3, 5>;
            integer_pack_test<integer_pack_shift_t<1, 3, pack_t>, l_expected_t>();

            // right to left shift - dst to 0
            using r_expected_t = integer_pack<int, 5, 7, 11, 1, 3>;
            integer_pack_test<integer_pack_shift_t<2, 0, pack_t>, r_expected_t>();

            // shift 0 to end - 1
            using shift_0e_t = integer_pack<long, -3, -5, 1, -7, 11, 12, 8, 9>;
            using expected_shift_0e_t = integer_pack<long, -5, 1, -7, 11, 12, 8, 9, -3>;
            using result_shift_0e_t = integer_pack_shift_t<0, 7, shift_0e_t>;
            integer_pack_test<result_shift_0e_t, expected_shift_0e_t>();

            // shift end - 1 to 0
            using shift_e0_t = integer_pack<long, -3, -5, 1, -7, 11, 12, 8, 9>;
            using expected_shift_e0_t = integer_pack<long, 9, -3, -5, 1, -7, 11, 12, 8>;
            using result_shift_e0_t = integer_pack_shift_t<7, 0, shift_e0_t>;
            integer_pack_test<result_shift_e0_t, expected_shift_e0_t>();

            // shift src to 0
            using shift_t0 = integer_pack<long, -3, -5, 1, -7>;
            using expected_shift_t0 = integer_pack<long, 1, -7, -3, -5>;
            using result_shift_t0 = integer_pack_shift_t<1, 3, shift_t0>;
            integer_pack_test<result_shift_t0, expected_shift_t0>();
        }
    }
}

Running tests:

int main()
{
    ct::test::test_integer_pack_modifiers();
}

Implementation

// integer_pack_extract, integer_pack_reverse
namespace ct
{
    namespace impl
    {
        template<class T, T... ints, std::size_t... is>
        constexpr auto extract(integer_pack<T, ints...>, index_pack<is...>) noexcept
        {
            constexpr T integers[] = { ints... };
            return integer_pack<T, integers[is]...>{};
        }
    }

    template<std::size_t src, std::size_t dst, class IntegerPack>
    struct integer_pack_extract
    {
    private:
        static_assert(integer_pack_size<IntegerPack>::value != 0,
            "integer_pack_extract: empty integer pack");

        static_assert(src >= 0 && src < integer_pack_size<IntegerPack>::value,
            "integer_pack_extract: src index out of bounds");

        static_assert(dst >= 0 && dst < integer_pack_size<IntegerPack>::value,
            "integer_pack_extract: dst index out of bounds");

    public:
        using type = decltype(
            impl::extract(IntegerPack{}, make_index_range<src, dst>{}));
    };

    template<std::size_t src, std::size_t dst, class IntegerPack>
    using integer_pack_extract_t = typename integer_pack_extract
    <
        src, dst, IntegerPack
    >::type;

    template<class IntegerPack>
    struct integer_pack_reverse
        : integer_pack_extract_t
        <
            integer_pack_size<IntegerPack>::value - 1, 0, IntegerPack
        >
    {};

    template<class T>
    struct integer_pack_reverse<integer_pack<T>>
    {
        using type = integer_pack<T>;
    };

    template<class IntegerPack>
    using integer_pack_reverse_t = typename integer_pack_reverse<IntegerPack>::type;
}

// 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::integer_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_minimum, integer_pack_maximum
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<class IntegerPack>
    struct integer_pack_minimum
        : std::integral_constant
        <
            typename IntegerPack::integer_type,
            impl::find_integer(impl::smallest{}, IntegerPack{})
        >
    {
        static_assert(integer_pack_size<IntegerPack>::value != 0,
            "integer_pack_minimum: empty integer pack");
    };

    template<class IntegerPack>
    constexpr auto integer_pack_minimum_v = integer_pack_minimum<IntegerPack>::value;

    template<class IntegerPack>
    struct integer_pack_maximum
        : std::integral_constant
        <
            typename IntegerPack::integer_type,
            impl::find_integer(impl::largest{}, IntegerPack{})
        >
    {
        static_assert(integer_pack_size<IntegerPack>::value != 0,
            "integer_pack_maximum: empty integer pack");
    };

    template<class IntegerPack>
    constexpr auto integer_pack_maximum_v = integer_pack_maximum<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...>
    >
        : 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;
}

// index_sequence_shift, integer_pack_shift
namespace ct
{
    namespace impl
    {
        // right to left shift
        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>
            >;
        };

        // left to right shift
        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:
        static_assert(src < integer_pack_size<IndexSequence>::value,
            "index_sequence_shift: src index out of range");

        static_assert(dst < integer_pack_size<IndexSequence>::value,
            "index_sequence_shift: dst index out of range");

        static_assert(integer_pack_size<IndexSequence>::value != 0,
            "index_sequence_shift: empty index sequence");

        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>
    {
        static_assert(integer_pack_size<IndexSequence>::value != 0,
            "index_sequence_shift: empty index sequence");

        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::integer_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_offset
namespace ct
{
    template<std::size_t offset, class IntegerPack, bool increasing, class = void>
    struct integer_pack_offset;

    template<class IntegerPack, bool increasing>
    struct integer_pack_offset<0, IntegerPack, increasing>
    {
        using type = IntegerPack;
    };

    template<std::size_t offset, class T, T... ints>
    struct integer_pack_offset
    <
        offset,
        integer_pack<T, ints...>,
        true,
        std::enable_if_t<(offset != 0)>
    >
    {
        static_assert(sizeof...(ints) != 0,
            "integer_pack_offset: empty integer pack");

        static_assert(offset <= std::numeric_limits<T>::max(),
            "integer_pack_offset: offset overflow");

        using type = integer_pack<T, (ints + static_cast<T>(offset))...>;
    };

    template<std::size_t offset, class T, T... ints>
    struct integer_pack_offset
    <
        offset,
        integer_pack<T, ints...>,
        false,
        std::enable_if_t<(offset != 0)>
    >
    {
        static_assert(sizeof...(ints) != 0,
            "integer_pack_offset: empty integer pack");

        static_assert(offset <= std::numeric_limits<T>::max(),
            "integer_pack_offset: offset overflow");

        using type = integer_pack<T, (ints - static_cast<T>(offset))...>;
    };

    template<std::size_t offset, class IntegerPack, bool increasing>
    using integer_pack_offset_t = typename integer_pack_offset
    <
        offset, IntegerPack, increasing
    >::type;
}

Part 3

Part 3 consists of sorting, and set operations:

  1. Sorting.
  2. Union.
  3. Intersection.
  4. Complement.

Overview

  • integer_pack_apply<Function, IntegerPacks...>

This type applies the functor template argument Function to the variadic template argument IntegerPacks. It uses an internal constexpr function to call the functor and extract the output into an integer_pack<T, ints...> where ints... is the output of the functor when called as Function{}(IntegerPacks...).

Without integer_pack_apply<Function, IntegerPacks...>, we write:

template<class IntegerPack, class = void>
struct integer_pack_sort
{
private:
    template<std::size_t... is>
    static constexpr auto apply(index_pack<is...>) noexcept
    {
        constexpr auto integers{ impl::counting_sort(IntegerPack{}) };
        return integer_pack
        <
            impl::array_value_type_t<decltype(integers)>, integers[is]...
        >{};
    }

public:
    using type = decltype(
        apply(make_index_sequence<impl::array_size(
                decltype(impl::counting_sort(IntegerPack{})){})>{}));
};

With integer_pack_apply<Function, IntegerPacks...>, we can instead write:

template<class IntegerPack, class = void>
struct integer_pack_sort
{
private:
    struct sort_t
    {
        constexpr auto operator()() const
        {
            return impl::counting_sort(IntegerPack{});
        }
    };

public:
    using type = impl::integer_pack_apply_t<sort_t>;
};

We can see that this is much more concise and easy to read. I believe that it is worth the extra layer because it automatises the pattern and conforms to DRY; it is used in the remaining types.

  • integer_pack_sort<IntegerPack>

Sorts the template argument integer pack by ascending order using a simple counting sort.

Reverse sort can be done with integer_pack_reverse<IntegerPack>: \$reverse(sort(P))\$

I use a counting sort because I expect most integer packs to be created using the make sequence/make range templates, thus there won't (normally anyway) be much of a drawback associated with a counting sort when there is a large difference between the size of the pack and the highest value in the pack.

  • integer_pack_union<LIntegerPack, RIntegerPack>

Performs the set union of two integer packs. The output set is sorted. The output set will have the integer type returned by applying std::common_type<T, U> to the integer types of the left and right integer packs.

  • integer_pack_intersection<LIntegerPack, RIntegerPack>

Performs the set intersection of two integer packs. The output set is sorted. The output set will have the integer type returned by applying std::common_type<T, U> to the integer types of the left and right integer packs.

  • integer_pack_complement<LIntegerPack, RIntegerPack>

Performs the set intersection of two integer packs. The output set is sorted. The output set will have the same integer type as the integer pack specified as the left template argument.

Tests

// integer_pack_test_sort_and_set_operations
namespace ct
{
    namespace test
    {
        template<template<class...> class Test, class Expected, class... Input>
        constexpr void integer_pack_test() noexcept
        {
            static_assert(std::is_same<Test<Input...>, Expected>::value);
        }

        constexpr void integer_pack_test_sort_and_set_operations() noexcept
        {
            // sorting
            using sort_t = integer_pack<long, 5, 3, 17, -15, 8, -3, -23, 1>;
            using expected_sort_t = integer_pack<long, -23, -15, -3, 1, 3, 5, 8, 17>;
            integer_pack_test<integer_pack_sort_t, expected_sort_t, sort_t>();

            // sorting 0 : empty integer pack
            using sort_t0 = integer_pack<long>;
            using expected_sort_t0 = integer_pack<long>;
            integer_pack_test<integer_pack_sort_t, expected_sort_t0, sort_t0>();

            // union
            using l_union_t = integer_pack<int, 1, -2, 3, -4>;
            using r_union_t = integer_pack<long, -1, -2, -3, -4>;
            using expected_union_t = integer_pack<long, -4, -3, -2, -1, 1, 3>;
            integer_pack_test
            <
                integer_pack_union_t, expected_union_t, l_union_t, r_union_t
            >();

            // union 0 : empty left integer pack
            using l_union_t0 = integer_pack<long>;
            using r_union_t0 = integer_pack<int, -1, -2, -3, -4>;
            using expected_union_t0 = integer_pack<long, -1, -2, -3, -4>;
            integer_pack_test
            <
                integer_pack_union_t, expected_union_t0, l_union_t0, r_union_t0
            >();

            // union 1 : empty right integer pack
            using l_union_t1 = integer_pack<long>;
            using r_union_t1 = integer_pack<int, -1, -2, -3, -4>;
            using expected_union_t1 = integer_pack<long, -1, -2, -3, -4>;
            integer_pack_test
            <
                integer_pack_union_t, expected_union_t1, l_union_t1, r_union_t1
            >();

            // union 2 : empty integer packs
            using l_union_t2 = integer_pack<int>;
            using r_union_t2 = integer_pack<long>;
            using expected_union_t2 = integer_pack<long>;
            integer_pack_test
            <
                integer_pack_union_t, expected_union_t1, l_union_t1, r_union_t1
            >();

            // intersection
            using l_ntrsct_t = integer_pack<int, -5, 2, 8, 6, 3>;
            using r_ntrsct_t = integer_pack<int, 6, 4, 2, 2, 3, 9>;
            using expected_ntrsct_t = integer_pack<int, 2, 3, 6>;
            integer_pack_test
            <
                integer_pack_instersection_t, expected_ntrsct_t, l_ntrsct_t, r_ntrsct_t
            >();

            // intersection 0 : empty left integer pack
            using l_ntrsct_t0 = integer_pack<long>;
            using r_ntrsct_t0 = integer_pack<int, 6, 4, 2, 2, 3, 9>;
            using expected_ntrsct_t0 = integer_pack<long>;
            integer_pack_test
            <
                integer_pack_instersection_t,
                expected_ntrsct_t0, l_ntrsct_t0, r_ntrsct_t0
            >();

            // intersection 1 : empty right integer pack
            using l_ntrsct_t1 = integer_pack<int, -5, 2, 8, 6, 3>;
            using r_ntrsct_t1 = integer_pack<long>;
            using expected_ntrsct_t1 = integer_pack<long>;
            integer_pack_test
            <
                integer_pack_instersection_t,
                expected_ntrsct_t1, l_ntrsct_t1, r_ntrsct_t1
            >();

            // intersection 2 : empty integer packs
            using l_ntrsct_t2 = integer_pack<int>;
            using r_ntrsct_t2 = integer_pack<long>;
            using expected_ntrsct_t2 = integer_pack<long>;
            integer_pack_test
            <
                integer_pack_instersection_t,
                expected_ntrsct_t2, l_ntrsct_t2, r_ntrsct_t2
            >();

            // complement
            using l_comp_t0 = integer_pack<char, -1, 0, 1>;
            using r_comp_t0 = integer_pack<int, 1, 2, 3, -1, 4>;
            using expected_comp_t0 = integer_pack<char, 0>;
            integer_pack_test
            <
                integer_pack_complement_t, expected_comp_t0, l_comp_t0, r_comp_t0
            >();

            using l_comp_t1 = integer_pack<int, -1, 1, 2, 3, 4>;
            using r_comp_t1 = integer_pack<char, 0, 1>;
            using expected_comp_t1 = integer_pack<int, -1, 2, 3, 4>;
            integer_pack_test
            <
                integer_pack_complement_t, expected_comp_t1, l_comp_t1, r_comp_t1
            >();

            // complement 2 : empty left integer pack
            using l_comp_t2 = integer_pack<int>;
            using r_comp_t2 = integer_pack<char, 0, 1>;
            using expected_comp_t2 = integer_pack<int>;
            integer_pack_test
            <
                integer_pack_complement_t, expected_comp_t2, l_comp_t2, r_comp_t2
            >();

            // complement 3 : empty right integer pack
            using l_comp_t3 = integer_pack<int, -1, 1, 2, 3, 4>;
            using r_comp_t3 = integer_pack<char>;
            using expected_comp_t3 = integer_pack<int, -1, 1, 2, 3, 4>;
            integer_pack_test
            <
                integer_pack_complement_t, expected_comp_t3, l_comp_t3, r_comp_t3
            >();

            // complement 4 : empty integer packs
            using l_comp_t4 = integer_pack<int>;
            using r_comp_t4 = integer_pack<char>;
            using expected_comp_t4 = integer_pack<int>;
            integer_pack_test
            <
                integer_pack_complement_t, expected_comp_t4, l_comp_t4, r_comp_t4
            >();
        }
    }
}

Running the tests:

int main()
{
    ct::test::integer_pack_test_sort_and_set_operations();
}

Additional usage/test

This simple example shows how one would create the symmetric difference operation of two integer packs using the already existing operations:

The symmetric difference of two sets A and B is the set of elements comprised of:

  • All elements in set A, not in set B.
  • All elements in set B, not in set A.

The algorithm is simple: \$sort(concatenate(complement(A, B), complement(B, A)))\$

template<class LIntegerPack, class RIntegerPack>
struct integer_pack_symmetric_difference
{
private:
    using l_comp_t = ct::integer_pack_complement_t<LIntegerPack, RIntegerPack>;
    using r_comp_t = ct::integer_pack_complement_t<RIntegerPack, LIntegerPack>;

public:
    /*note: concatenate will apply std::common_type; left as is for demo*/
    using type = ct::integer_pack_sort_t
    <
        ct::integer_pack_concatenate_t<l_comp_t, r_comp_t>
    >;
};

template<class LIntegerPack, class RIntegerPack>
using integer_pack_symmetric_difference_t = typename integer_pack_symmetric_difference
<
    LIntegerPack, RIntegerPack
>::type;

int main()
{
    using namespace ct;

    using l_symm_diff_t = integer_pack<int, 0, 1, 2, 3>;
    using r_symm_diff_t = integer_pack<int, -1, 1, 2, 3, 4>;
    using expected_symm_diff_t = integer_pack<int, -1, 0, 4>;
    using result_symm_diff_t = integer_pack_symmetric_difference_t
    <
        l_symm_diff_t, r_symm_diff_t
    >;

    static_assert(std::is_same_v<result_symm_diff_t, expected_symm_diff_t>);
}

Implementation

Note: Include guard omitted.

// integer_pack_apply
namespace ct
{
    namespace impl
    {
        template<class T, std::size_t n>
        constexpr auto array_value_type(T const(&)[n]) noexcept -> T;

        template<class T, std::size_t n>
        constexpr auto array_value_type(std::array<T, n> const&) noexcept -> T;

        template<class T>
        using array_value_type_t = decltype(array_value_type(std::declval<T&>()));

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

        template<class Function, class... IntegerPacks>
        struct integer_pack_apply
        {
        private:
            using array_t = decltype(Function{}(IntegerPacks{}...));

            template<std::size_t... is>
            static constexpr auto apply(index_pack<is...>) noexcept
            {
                constexpr auto integers{ Function{}(IntegerPacks{}...) };
                return integer_pack<array_value_type_t<array_t>, integers[is]...>{};
            }

        public:
            using type = decltype(apply(make_index_sequence<array_size(array_t{})>{}));
        };

        template<class Function, class... IntegerPacks>
        using integer_pack_apply_t = typename integer_pack_apply
        <
            Function, IntegerPacks...
        >::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>{});
        }

        template<class T, T... ints>
        constexpr auto counting_sort(integer_pack<T, ints...>) noexcept
        {
            using pack_t = integer_pack<T, ints...>;

            constexpr auto min{ integer_pack_minimum<pack_t>::value };
            constexpr auto has_negative{ min < 0 };

            using offset_pack_t = std::conditional_t
            <
                has_negative,
                integer_pack_offset_t<-min, pack_t, true>,
                pack_t
            >;

            constexpr auto offset{ min < 0 ? -min : 0 };

            T integers[] = { (ints + offset)... };

            constexpr auto size{ integer_pack_maximum<offset_pack_t>::value + 1 };
            std::size_t counts[size] = {};

            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 < size; i++)
                for (std::size_t j{ 0 }; j < counts[i]; j++)
                    sorted_integers[k++] = static_cast<T>(i) - offset;

            return to_std_array(sorted_integers);
        }
    }

    template<class IntegerPack>
    struct integer_pack_sort
    {
    private:
        struct sort_t
        {
            constexpr auto operator()() const
            {
                return impl::counting_sort(IntegerPack{});
            }
        };

    public:
        using type = impl::integer_pack_apply_t<sort_t>;
    };

    template<class T>
    struct integer_pack_sort<integer_pack<T>>
    {
        using type = integer_pack<T>;
    };

    template<class IntegerPack>
    using integer_pack_sort_t = typename integer_pack_sort<IntegerPack>::type;
}

// integer_pack_union
namespace ct
{
    namespace impl
    {
        template<class T, std::size_t n>
        struct make_array
        {
            template<class U>
            static constexpr auto from_union(T const offset, U const& map) noexcept
            {
                T union_integers[n] = {};

                for (std::size_t i{ 0 }, k{ 0 }; i < map.size(); ++i)
                    if (map[i])
                        union_integers[k++] = static_cast<T>(i) - offset;

                return to_std_array(union_integers);
            }

            template<class U, class V>
            static constexpr auto from_intersection(
                T const offset, U const& l_map, V const& r_map) noexcept
            {
                T intersection_integers[n] = {};

                for (std::size_t i{ 0 }, k{ 0 }; i < r_map.size(); ++i)
                    if (l_map[r_map[i]])
                        intersection_integers[k++] = r_map[i] - offset;

                return to_std_array(intersection_integers);
            }

            template<class U, class V>
            static constexpr auto from_complement(
                T const offset, T const max, U const& l_map, V const& r_map) noexcept
            {
                T complement_integers[n] = {};

                for (std::size_t i{ 0 }, k{ 0 }; i < l_map.size(); i++)
                    if (l_map[i] > max || !r_map[l_map[i]])
                        complement_integers[k++] = l_map[i] - offset;

                return to_std_array(complement_integers);
            }
        };

        template<class T>
        struct make_array<T, 0>
        {
            template<class U>
            static constexpr auto from_union(T const, U const&) noexcept
            {
                return std::array<T, 0>{};
            }

            template<class U, class V>
            static constexpr auto from_intersection(
                T const, U const&, V const&) noexcept
            {
                return std::array<T, 0>{};
            }

            template<class U, class V>
            static constexpr auto from_complement(
                T const, T const, U const&, V const&) noexcept
            {
                return std::array<T, 0>{};
            }
        };

        template<class T, T... ints>
        constexpr auto make_found_integer_map(integer_pack<T, ints...>) noexcept
        {
            using pack_t = integer_pack<T, ints...>;

            constexpr auto min{ integer_pack_minimum<pack_t>::value };
            constexpr auto max{ integer_pack_maximum<pack_t>::value };

            constexpr auto offset{ min < 0 ? -min : 0 };
            constexpr T integers[] = { (ints + offset)... };

            bool found[max + offset + 1] = {};

            for (std::size_t i{ 0 }; i < sizeof...(ints); ++i)
                found[static_cast<std::size_t>(integers[i])] = true;

            return to_std_array(found);
        }

        template<class T>
        constexpr auto found_integer_map_size(T const& map) noexcept
        {
            std::size_t size{ 0 };

            for (typename T::size_type i{ 0 }; i < map.size(); ++i)
                if (map[i])
                    ++size;

            return size;
        }

        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 integer_type = std::common_type_t<T, U>;

            constexpr auto min
            {
                integer_pack_minimum
                <
                    integer_pack<integer_type, l_ints..., r_ints...>
                >::value
            };

            constexpr auto map
            {
                make_found_integer_map(
                    integer_pack<integer_type, l_ints..., r_ints...>{})
            };

            return make_array
            <
                integer_type, found_integer_map_size(map)
            >::from_union(min < 0 ? -min : 0, map);
        }
    }

    template<class LIntegerPack, class RIntegerPack, class = void>
    struct integer_pack_union
    {
    private:
        struct union_t
        {
            constexpr auto operator()() const
            {
                return impl::integer_pack_union(LIntegerPack{}, RIntegerPack{});
            };
        };

    public:
        using type = impl::integer_pack_apply_t<union_t>;
    };

    template<class T, T... ints, class U>
    struct integer_pack_union
    <
        integer_pack<T, ints...>,
        integer_pack<U>,
        std::enable_if_t<sizeof...(ints) != 0>
    >
    {
        using type = integer_pack<std::common_type_t<T, U>, ints...>;
    };

    template<class T, class U, U... ints>
    struct integer_pack_union
    <
        integer_pack<T>,
        integer_pack<U, ints...>,
        std::enable_if_t<sizeof...(ints) != 0>
    >
    {
        using type = integer_pack<std::common_type_t<T, U>, ints...>;
    };

    template<class T, class U>
    struct integer_pack_union<integer_pack<T>, integer_pack<U>>
    {
        using type = integer_pack<std::common_type_t<T,U>>;
    };

    template<class LIntegerPack, class RIntegerPack>
    using integer_pack_union_t = typename integer_pack_union
    <
        LIntegerPack, RIntegerPack
    >::type;
}

// integer_pack_instersection
namespace ct
{
    namespace impl
    {
        template<class T, T... ints>
        constexpr auto to_std_array(integer_pack<T, ints...>) noexcept
        {
            return std::array<T, sizeof...(ints)>{ ints... };
        }

        template<class T, class U>
        constexpr auto intersection_size(T const& lhs, U const& rhs) noexcept
        {
            std::size_t size{ 0 };

            for (std::size_t i{ 0 }; i < rhs.size(); ++i)
                if (lhs[rhs[i]])
                    ++size;

            return size;
        }

        template<class LIntegerPack, class RIntegerPack>
        constexpr auto integer_pack_equalizer_offset() noexcept
        {
            constexpr auto l{ integer_pack_minimum<LIntegerPack>::value };
            constexpr auto r{ integer_pack_minimum<RIntegerPack>::value };

            /*
            determining the proper offset:
                if both minimums are negative, pick smallest
                if one minimum is negative, and the other positive, pick negative
                if neither minimum is negative, offset is 0
                whenever a negative minimum is picked, make it positive (*-1)

            algorithm:
                if (l < 0 && r < 0)
                {
                    offset = l < r ? -l : -r;
                }
                else if (l < 0)
                {
                    offset = -l;
                }
                else if (r < 0)
                {
                    offset = -r;
                }
                else
                {
                    offset = 0;
                }
            */
            return l < 0 ? (r < 0 ? (l < r ? -l : -r) : -l) : (r < 0 ? -r : 0);
        }

        template<class T, T... ints>
        constexpr auto remove_repetitions(integer_pack<T, ints...>) noexcept
        {
            using pack_t = integer_pack<T, ints...>;

            constexpr auto map{ make_found_integer_map(pack_t{}) };
            constexpr auto min{ integer_pack_minimum<pack_t>::value };
            constexpr auto offset{ min < 0 ? -min : 0 };

            T integers[found_integer_map_size(map)] = {};

            for (std::size_t i{ 0 }, k{ 0 },
                sz{ integer_pack_maximum<pack_t>::value + offset + 1 };
                i < sz;
                ++i)
                if (map[i])
                    integers[k++] = static_cast<T>(i) - offset;

            return to_std_array(integers);
        }

        template<class IntegerPack>
        struct integer_pack_remove_repetitions_and_sort
        {
        private:
            struct remove_repetitions_t
            {
                constexpr auto operator()() const
                {
                    return remove_repetitions(IntegerPack{});
                };
            };

        public:
            using type = impl::integer_pack_apply_t<remove_repetitions_t>;
        };

        template<class IntegerPack>
        using integer_pack_remove_repetitions_and_sort_t =
            typename integer_pack_remove_repetitions_and_sort<IntegerPack>::type;

        template<class LIntegerPack, class RIntegerPack>
        constexpr auto make_intersection() noexcept
        {
            using integer_type = std::common_type_t
            <
                typename LIntegerPack::integer_type,
                typename RIntegerPack::integer_type
            >;

            constexpr auto l_min{ integer_pack_minimum<LIntegerPack>::value };
            constexpr auto r_min{ integer_pack_minimum<RIntegerPack>::value };
            constexpr auto offset
            {
                integer_pack_equalizer_offset<LIntegerPack, RIntegerPack>()
            };

            using l_offset_t = std::conditional_t
            <
                (l_min < r_min),
                integer_pack_offset_t<offset, LIntegerPack, true>,
                integer_pack_offset_t<offset, LIntegerPack, true>
            >;

            using r_offset_t = std::conditional_t
            <
                (l_min < r_min),
                integer_pack_offset_t<offset, RIntegerPack, true>,
                integer_pack_offset_t<offset, RIntegerPack, true>
            >;

            using l_pack_t = std::conditional_t
            <
                (integer_pack_maximum<l_offset_t>::value >
                    integer_pack_maximum<r_offset_t>::value),
                l_offset_t,
                r_offset_t
            >;

            using r_pack_t = std::conditional_t
            <
                std::is_same<l_pack_t, l_offset_t>::value, r_offset_t, l_offset_t
            >;

            constexpr auto l_map{ make_found_integer_map(l_pack_t{}) };
            constexpr auto r_map
            {
                to_std_array(integer_pack_remove_repetitions_and_sort_t<r_pack_t>{})
            };

            return make_array
            <
                integer_type, intersection_size(l_map, r_map)
            >::from_intersection(offset, l_map, r_map);
        }
    }

    template<class LIntegerPack, class RIntegerPack>
    struct integer_pack_instersection
    {
    private:
        struct intersection_t
        {
            constexpr auto operator()() const
            {
                return impl::make_intersection<LIntegerPack, RIntegerPack>();
            };
        };

    public:
        using type = impl::integer_pack_apply_t<intersection_t>;
    };

    template<class T, class RIntegerPack>
    struct integer_pack_instersection<integer_pack<T>, RIntegerPack>
    {
        using type = integer_pack
        <
            std::common_type_t<T, typename RIntegerPack::integer_type>
        >;
    };

    template<class LIntegerPack, class T>
    struct integer_pack_instersection<LIntegerPack, integer_pack<T>>
    {
        using type = integer_pack
        <
            std::common_type_t<T, typename LIntegerPack::integer_type>
        >;
    };

    template<class T, class U>
    struct integer_pack_instersection<integer_pack<T>, integer_pack<U>>
    {
        using type = integer_pack<std::common_type_t<T, U>>;
    };

    template<class LIntegerPack, class RIntegerPack>
    using integer_pack_instersection_t = typename integer_pack_instersection
    <
        LIntegerPack, RIntegerPack
    >::type;
}

// integer_pack_complement
namespace ct
{
    namespace impl
    {
        template<class T, class U, class V>
        constexpr auto complement_size(
            T const max, U const& l_map, V const& r_map) noexcept
        {
            std::size_t size{ 0 };

            /*
            l_map: <0, 1, 2, 3, 4> -> only converted

            r_map: <1, 2, 3> -> r_map is the one that needs to be mapped out

            complement is l_map[i] (iterator on l_map)
                -> l_map[i] < max(r_map) && !r_map[l_map[i]]
            */

            for (std::size_t i{ 0 }; i < l_map.size(); i++)
                if (l_map[i] > max || !r_map[l_map[i]])
                    ++size;

            return size;
        }

        template<class LIntegerPack, class RIntegerPack>
        constexpr auto make_complement() noexcept
        {
            constexpr auto offset
            {
                integer_pack_equalizer_offset<LIntegerPack, RIntegerPack>()
            };

            using l_pack_t = integer_pack_offset_t<offset, LIntegerPack, true>;
            using r_pack_t = integer_pack_offset_t<offset, RIntegerPack, true>;

            constexpr auto l_map{ to_std_array(l_pack_t{}) };
            constexpr auto r_map{ make_found_integer_map(r_pack_t{}) };

            constexpr auto r_map_max{ integer_pack_maximum<r_pack_t>::value };

            return make_array
            <
                typename LIntegerPack::integer_type,
                complement_size(r_map_max, l_map, r_map)
            >::from_complement(offset, r_map_max, l_map, r_map);
        }
    }

    template<class LIntegerPack, class RIntegerPack>
    struct integer_pack_complement
    {
    private:
        struct complement_t
        {
            constexpr auto operator()() const
            {
                return impl::make_complement<LIntegerPack, RIntegerPack>();
            };
        };

    public:
        using type = impl::integer_pack_apply_t<complement_t>;
    };

    template<class T, class RIntegerPack>
    struct integer_pack_complement<integer_pack<T>, RIntegerPack>
    {
        using type = integer_pack<T>;
    };

    template<class LIntegerPack, class T>
    struct integer_pack_complement<LIntegerPack, integer_pack<T>>
    {
        using type = LIntegerPack;
    };

    template<class T, class U>
    struct integer_pack_complement<integer_pack<T>, integer_pack<U>>
    {
        using type = integer_pack<T>;
    };

    template<class LIntegerPack, class RIntegerPack>
    using integer_pack_complement_t = typename integer_pack_complement
    <
        LIntegerPack, RIntegerPack
    >::type;
}
\$\endgroup\$
1
  • 2
    \$\begingroup\$ This is a really well-written question; I like the way you have broken it into coherent chunks and provided plenty of explanation. I just wish my template-fu were strong enough to give this the review it deserves! \$\endgroup\$ Commented May 24, 2017 at 15:12

1 Answer 1

2
\$\begingroup\$

First of all, your code is very impressive. I was going through it to improve my template programming skills when I saw a potential issue.

The following test code works when char is signed type.

using make_range_3_to_neg9 = make_integer_range<char, 3, -9, 3>;
using expected_make_range_3_to_neg9 = integer_pack<char, 3, 0, -3, -6, -9>;
integer_pack_test<make_range_3_to_neg9, expected_make_range_3_to_neg9>();

But it fails at compile time with static_assert if you use unsigned char. If char happens to be an unsigned type on a platform, you will run into the same problem.

I would disable integer_pack<char, ...> since char is not guaranteed to be a signed type.

template<char... ints>
struct integer_pack<char, ints...>
{
    static_assert(false, "integer_pack: Disabled for char");
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.