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

Compile-time-fixed templated integer range

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

Problem

This is a follow-up of an old question by @LokiAstari, modified for the current community challenge. The idea is to provide a compile-time integer range. I applied all the modifications that I proposed to Loki at the time and tried to write a class as close as possible to the standard library class std::integer_sequence:

  • This is a [begin, end) range instead of a [begin, end] one.



  • The range can be ascending or descending.



  • The class is templated so that it is possible to choose the integer type to use.



  • In order to match the standard library utilities, I also provide the template index_range which is an alias of integer_range for the type std::size_t.



Here is the implementation:

#include 
#include 
#include 
#include 

namespace details
{
    ////////////////////////////////////////////////////////////
    // implementation details

    template
    struct increasing_integer_range;

    template
    struct increasing_integer_range, Begin>:
        std::integer_sequence
    {};

    template
    struct decreasing_integer_range;

    template
    struct decreasing_integer_range, Begin>:
        std::integer_sequence
    {};
}

////////////////////////////////////////////////////////////
// integer_range

template
struct integer_range;

template
struct integer_range:
    details::increasing_integer_range,
        Begin>
{};

template
struct integer_range:
    details::decreasing_integer_range,
        Begin>
{};

template
using index_range = integer_range;


I don't ask for a specific kind of review. If you see anything that can be improved, do not hesitate.

And as a bonus, take this test cases. Since integer_range uses some heavy template wizardry, I had troubles coming with decent test cases that are easy enough to read, write and understand. They are not really beautiful, but they work as expected; that may help you if you want to try modifications:

```
template
void test(std::integer_sequence)
{
static_assert(N == 0, "");
}

te

Solution

IMO, your test cases would be vastly more readable if you wrote them simply in terms of std::is_same. If you were going to write a lot of test cases, you could wrap all that boilerplate up in a macro such as

#define TEST(type, start, end, ...) \
    static_assert(std::is_same, \
        std::integer_sequence >::value)


which reduces your test cases to the tiny amount of code they actually are.

TEST(int, 8,8);
TEST(int, 0,5, 0,1,2,3,4);
TEST(int, 5,0, 5,4,3,2,1);
TEST(int, -3,2, -3,-2,-1,0,1);
TEST(int, -1,-4, -1,-2,-3);
TEST(int, 3,-8, 3,2,1,0,-1,-2,-3,-4,-5,-6,-7);


This shorter formulation incidentally makes it more obvious that you didn't test any corner cases or types other than int — which is why I'm a strong proponent of "shorter code is better code." We've now made the code short enough that we can see where the bugs are.

Okay, let's move on to looking at the actual code. It actually looks very clean and simple. Admittedly I did think at first that you were doing the bad O(n) approach where integer_range inherits from integer_range; it took me a moment to see that you were in fact doing the right thing. I see only two issues with

template
struct increasing_integer_range, Begin>:
    std::integer_sequence
{};


The first problem is that our new test cases don't pass, because your increasing_integer_range is not actually a synonym for std::integer_sequence; instead it's merely derived from std::integer_sequence. This is good enough for the average codebase, but for a library implementation we'd want to make sure that the two types are actually equal. Fortunately, C++11 template typedefs make this easy. Rewrite it as

namespace details
{
    template
    struct integer_range_impl;

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

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

template
using integer_range = typename details::integer_range_impl,
    Begin,
    (Begin::type;

template
using index_range = integer_range;


The second problem is that this implementation does not work for some obvious corner cases, such as integer_range, because in general the result of End-Begin is not guaranteed to be representable in Int.

Fixing the second problem is left as an exercise for the reader, because I believe it's pretty darn hard. I think you might have to abandon the (really clever) idea of using std::integer_sequence to do your heavy lifting, and resign yourself to reimplementing integer_sequence something like Xeo did here except harder.

EDIT TO ADD a third "problem" I just thought of: The name integer_range could be considered misleading. If I were proposing this for the standard library, I'd name it make_integer_range, by analogy with make_integer_sequence.

Code Snippets

#define TEST(type, start, end, ...) \
    static_assert(std::is_same< \
        polder::integer_range<type, start, end>, \
        std::integer_sequence<type ,##__VA_ARGS__ > >::value)
TEST(int, 8,8);
TEST(int, 0,5, 0,1,2,3,4);
TEST(int, 5,0, 5,4,3,2,1);
TEST(int, -3,2, -3,-2,-1,0,1);
TEST(int, -1,-4, -1,-2,-3);
TEST(int, 3,-8, 3,2,1,0,-1,-2,-3,-4,-5,-6,-7);
template<typename Int, Int... N, Int Begin>
struct increasing_integer_range<Int, std::integer_sequence<Int, N...>, Begin>:
    std::integer_sequence<Int, N+Begin...>
{};
namespace details
{
    template<typename Int, typename, Int Begin, bool Increasing>
    struct integer_range_impl;

    template<typename Int, Int... N, Int Begin>
    struct integer_range_impl<Int, std::integer_sequence<Int, N...>, Begin, true> {
        using type = std::integer_sequence<Int, N+Begin...>;
    };

    template<typename Int, Int... N, Int Begin>
    struct integer_range_impl<Int, std::integer_sequence<Int, N...>, Begin, false> {
        using type = std::integer_sequence<Int, Begin-N...>;
    };
}

template<typename Int, Int Begin, Int End>
using integer_range = typename details::integer_range_impl<
    Int,
    std::make_integer_sequence<Int, (Begin<End) ? End-Begin : Begin-End>,
    Begin,
    (Begin<End) >::type;

template<std::size_t Begin, std::size_t End>
using index_range = integer_range<std::size_t, Begin, End>;

Context

StackExchange Code Review Q#59059, answer score: 5

Revisions (0)

No revisions yet.