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 ofinteger_range
for the typestd::size_t
.
Here is the implementation:
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
namespace details
{
////////////////////////////////////////////////////////////
// implementation details
template<typename Int, typename, Int Begin>
struct increasing_integer_range;
template<typename Int, Int... N, Int Begin>
struct increasing_integer_range<Int, std::integer_sequence<Int, N...>, Begin>:
std::integer_sequence<Int, N+Begin...>
{};
template<typename Int, typename, Int Begin>
struct decreasing_integer_range;
template<typename Int, Int... N, Int Begin>
struct decreasing_integer_range<Int, std::integer_sequence<Int, N...>, Begin>:
std::integer_sequence<Int, Begin-N...>
{};
}
////////////////////////////////////////////////////////////
// integer_range
template<typename Int, Int Begin, Int End,
bool Increasing=(Begin<End)>
struct integer_range;
template<typename Int, Int Begin, Int End>
struct integer_range<Int, Begin, End, true>:
details::increasing_integer_range<
Int,
std::make_integer_sequence<Int, End-Begin>,
Begin>
{};
template<typename Int, Int Begin, Int End>
struct integer_range<Int, Begin, End, false>:
details::decreasing_integer_range<
Int,
std::make_integer_sequence<Int, Begin-End>,
Begin>
{};
template<std::size_t Begin, std::size_t End>
using index_range = integer_range<std::size_t, Begin, End>;
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<std::size_t N>
void test(std::integer_sequence<int>)
{
static_assert(N == 0, "");
}
template<std::size_t N>
void test(std::integer_sequence<int, 0, 1, 2, 3, 4>)
{
static_assert(N == 1, "");
}
template<std::size_t N>
void test(std::integer_sequence<int, 5, 4, 3, 2, 1>)
{
static_assert(N == 2, "");
}
template<std::size_t N>
void test(std::integer_sequence<int, -3, -2, -1, 0, 1>)
{
static_assert(N == 3, "");
}
template<std::size_t N>
void test(std::integer_sequence<int, -1, -2, -3>)
{
static_assert(N == 4, "");
}
template<std::size_t N>
void test(std::integer_sequence<int, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7>)
{
static_assert(N == 5, "");
}
int main()
{
using namespace polder;
test<0>(integer_range<int, 8, 8>());
test<1>(integer_range<int, 0, 5>());
test<2>(integer_range<int, 5, 0>());
test<3>(integer_range<int, -3, 2>());
test<4>(integer_range<int, -1, -4>());
test<5>(integer_range<int, 3, -8>());
}
Note: if you're interested, I wrote a follow-up to this question which incorporates most of the suggestions from the accepted answer and adds the possibility to specify a Step
parameter.
range(start, end, increment)
and slicing notationlist[start:end:increment]
which also supports negative numbers and wraparound. Perhaps you could extend your C++ integer sequence implementation to do something similar? \$\endgroup\$range(5, 1)
won't return anything, whileinteger_range<5, 1>
will be astd::integer_sequence<5, 4, 3, 2>
. That means that either I have to choose whether ranges with a bad increment (e.g. negative increment for ascending range) yield an empty range or whether the increment is always positive (as it is now, but extended to any positive integer). \$\endgroup\$template<typename Int, /*other stuff*/>
is misleading at a passing glance; people used to seeingint
may overlook thetypename
part and think it's an integer param rather than a type param. If you are designing this to accept non integer numeric types, then you should changetypename Int
intotypename Number
ortypename TNumeric
or something suitably similar. \$\endgroup\$template<typename T, /* other stuff */>
. That could be both simple and sufficient. \$\endgroup\$