HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Template integer range, version 2

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
versionrangetemplateinteger

Problem

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 
#include 

namespace detail
{
    template
    struct integer_range_impl;

    template
    struct integer_range_impl, Begin, Step, true>
    {
        using type = std::integer_sequence;
    };

    template
    struct integer_range_impl, Begin, Step, false>
    {
        using type = std::integer_sequence;
    };
}

template
using make_integer_range = typename detail::integer_range_impl,
    Begin,
    Step,
    (Begin ::type;

template
using make_index_range = make_integer_range;


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

#include 
#include 
#include 
#include 

template
auto print(const std::array& arr, std::index_sequence)
    -> void
{
    int dummy[] = {
        (std::cout (arr)  arr;
    std::iota(std::begin(arr), std::end(arr), 0);

    // prints 15 12 9 6 3
    print(arr, make_index_range{});
}


I accept any kind of review, be it about style, correctness or possible improvements to the utility in general :)

Solution

My main objection to your code is:

Principle of Least Surprise

I was pretty surprised that:

make_index_range


is the sequence

std::index_sequence


given that it sure looks like we're going from 15 to 0 by 3s... which intuitively should produce std::index_sequence<>. 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.


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 
struct sequence_size
: std::conditional_t::value,
        std::integral_constant(0, (End - Begin)/Step)>,
        std::integral_constant= Begin) ? (End - Begin)/Step : 0>
        >
{ };


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 ::value>
        >
    struct make_integer_range;

    template 
    struct make_integer_range>
    {
        using type = std::integer_sequence;
    };
}

template 
using make_integer_range = typename detail::make_integer_range::type;

template 
using make_index_range = make_integer_range;


Add Default Step

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

template 
using make_integer_range = typename detail::make_integer_range::type;

template 
using make_index_range = make_integer_range;


Add Default Start

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

template 
using make_integer_range = typename detail::make_integer_range_variadic::type;

template 
using make_index_range = make_integer_range;


With:

template 
struct make_integer_range_variadic
: make_integer_range
{ };

template 
struct make_integer_range_variadic
: make_integer_range
{ };

template 
struct make_integer_range_variadic
: make_integer_range
{ };


This would allow:

make_index_range       ==> 
make_index_range    ==> 
make_index_range ==> 


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

Code Snippets

make_index_range<15u, 0u, 3u>
std::index_sequence<15, 12, 9, 6, 3>
make_integer_range<int, 15, 0, -3>.
template <class T, T Begin, T End, T Step>
struct sequence_size
: std::conditional_t<
        std::is_signed<T>::value,
        std::integral_constant<T, std::max<T>(0, (End - Begin)/Step)>,
        std::integral_constant<T, (End >= Begin) ? (End - Begin)/Step : 0>
        >
{ };
namespace detail {
    template <class T, T Begin, T End, T Step, 
        typename = std::make_integer_sequence<T, sequence_size<T, Begin, End, Step>::value>
        >
    struct make_integer_range;

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

template <class T, T Begin, T End, T Step>
using make_integer_range = typename detail::make_integer_range<T, Begin, End, Step>::type;

template <std::size_t Begin, std::size_t End, std::size_t Step>
using make_index_range = make_integer_range<std::size_t, Begin, End, Step>;

Context

StackExchange Code Review Q#101137, answer score: 7

Revisions (0)

No revisions yet.