Template integer range, version 2 This question is a follow-up. The original question was itself a follow-up to an older question by @LokiAstari.


  The idea is to provide a compile-time integer range. 


This version incorporates almost everything from the answers and comments to the previous iteration of the review (except the handling of corner cases such as INT_MAX, which is kind of hard). Along with the fixes comes a new feature: the possibility to specify the « step » of the range. Here is the new implementation:

#include &lt;cstddef&gt;
#include &lt;utility&gt;

namespace detail
{
    template&lt;
        typename Integer,
        typename SequenceBase,
        Integer Begin,
        Integer Step,
        bool IsIncreasing
    &gt;
    struct integer_range_impl;

    template&lt;
        typename Integer,
        Integer... N,
        Integer Begin,
        Integer Step
    &gt;
    struct integer_range_impl&lt;Integer, std::integer_sequence&lt;Integer, N...&gt;, Begin, Step, true&gt;
    {
        using type = std::integer_sequence&lt;Integer, (N * Step + Begin)...&gt;;
    };

    template&lt;
        typename Integer,
        Integer... N,
        Integer Begin,
        Integer Step
    &gt;
    struct integer_range_impl&lt;Integer, std::integer_sequence&lt;Integer, N...&gt;, Begin, Step, false&gt;
    {
        using type = std::integer_sequence&lt;Integer, (Begin - N * Step)...&gt;;
    };
}

template&lt;
    typename Integer,
    Integer Begin,
    Integer End,
    Integer Step
&gt;
using make_integer_range = typename detail::integer_range_impl&lt;
    Integer,
    std::make_integer_sequence&lt;
        Integer,
        ((Begin &lt; End ? End - Begin : Begin - End) - 1) / Step + 1
    &gt;,
    Begin,
    Step,
    (Begin &lt; End)
&gt;::type;

template&lt;std::size_t Begin, std::size_t End, std::size_t Step&gt;
using make_index_range = make_integer_range&lt;std::size_t, Begin, End, Step&gt;;


Here is an example of how this template integer range can be used:

#include &lt;array&gt;
#include &lt;iostream&gt;
#include &lt;iterator&gt;
#include &lt;numeric&gt;

template&lt;typename T, std::size_t N, std::size_t... Ind&gt;
auto print(const std::array&lt;T, N&gt;&amp; arr, std::index_sequence&lt;Ind...&gt;)
    -&gt; void
{
    int dummy[] = {
        (std::cout &lt;&lt; std::get&lt;Ind&gt;(arr) &lt;&lt; ' ', 0)...
    };
    (void) dummy;
}

int main()
{        
    std::array&lt;int, 20u&gt; arr;
    std::iota(std::begin(arr), std::end(arr), 0);

    // prints 15 12 9 6 3
    print(arr, make_index_range&lt;15u, 0u, 3u&gt;{});
}


I accept any kind of review, be it about style, correctness or possible improvements to the utility in general :)
My main objection to your code is:

Principle of Least Surprise

I was pretty surprised that:

make_index_range&lt;15u, 0u, 3u&gt;


is the sequence

std::index_sequence&lt;15, 12, 9, 6, 3&gt;


given that it sure looks like we're going from 15 to 0 by 3s... which intuitively should produce std::index_sequence&lt;&gt;. You already do correctly provide two type aliases, where make_index_range is an alias for make_integer_range... so if somebody wants a negative range, they should have to do:

make_integer_range&lt;int, 15, 0, -3&gt;.


That change will also simplify your implementation, which leads into...

Simplify, Simplify, Simplify

Once you drop the increasing/decreasing check, the code becomes simpler. We are always going from Begin to End by Step. 

First, let's determine the size of our sequence. For signed integers, we can just divide by Step and floor at 0. For unsigned integers, we have to ensure that End is at least as big as Begin:

template &lt;class T, T Begin, T End, T Step&gt;
struct sequence_size
: std::conditional_t&lt;
        std::is_signed&lt;T&gt;::value,
        std::integral_constant&lt;T, std::max&lt;T&gt;(0, (End - Begin)/Step)&gt;,
        std::integral_constant&lt;T, (End &gt;= Begin) ? (End - Begin)/Step : 0&gt;
        &gt;
{ };


And then, with a default template parameter, our detail::make_integer_range can just be a simple pack expansion. No need for separate cases for increasing/decreasing sequences, since that would be handled by simply having a negative Step for signed integral types:

namespace detail {
    template &lt;class T, T Begin, T End, T Step, 
        typename = std::make_integer_sequence&lt;T, sequence_size&lt;T, Begin, End, Step&gt;::value&gt;
        &gt;
    struct make_integer_range;

    template &lt;class T, T Begin, T End, T Step, T... Idx&gt;
    struct make_integer_range&lt;T, Begin, End, Step, std::integer_sequence&lt;T, Idx...&gt;&gt;
    {
        using type = std::integer_sequence&lt;T, Idx * Step + Begin...&gt;;
    };
}

template &lt;class T, T Begin, T End, T Step&gt;
using make_integer_range = typename detail::make_integer_range&lt;T, Begin, End, Step&gt;::type;

template &lt;std::size_t Begin, std::size_t End, std::size_t Step&gt;
using make_index_range = make_integer_range&lt;std::size_t, Begin, End, Step&gt;;


Add Default Step

Using 1 as a default is pretty standard, so let's just do that:

template &lt;class T, T Begin, T End, T Step=1&gt;
using make_integer_range = typename detail::make_integer_range&lt;T, Begin, End, Step&gt;::type;

template &lt;std::size_t Begin, std::size_t End, std::size_t Step=1&gt;
using make_index_range = make_integer_range&lt;std::size_t, Begin, End, Step&gt;;


Add Default Start

You could also provide a default beginning by having your aliases take a pack of ints. Something like:

template &lt;class T, T... Vals&gt;
using make_integer_range = typename detail::make_integer_range_variadic&lt;T, Vals...&gt;::type;

template &lt;std::size_t... Vals&gt;
using make_index_range = make_integer_range&lt;std::size_t, Vals...&gt;;


With:

template &lt;class T, T... Vals&gt;
struct make_integer_range_variadic
: make_integer_range&lt;T, Vals...&gt;
{ };

template &lt;class T, T End&gt;
struct make_integer_range_variadic&lt;T, End&gt;
: make_integer_range&lt;T, 0, End, 1&gt;
{ };

template &lt;class T, T Begin, T End&gt;
struct make_integer_range_variadic&lt;T, Begin, End&gt;
: make_integer_range&lt;T, Begin, End, 1&gt;
{ };


This would allow:

make_index_range&lt;5&gt;       ==&gt; &lt;0, 1, 2, 3, 4&gt;
make_index_range&lt;2, 5&gt;    ==&gt; &lt;2, 3, 4&gt;
make_index_range&lt;2, 5, 2&gt; ==&gt; &lt;2, 4&gt;


YMMV on whether or not you want to allow this, but just throwing this out as an option in case you do.