debugcppMinor
Compile-time-fixed templated integer range
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
Here is the implementation:
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
```
template
void test(std::integer_sequence)
{
static_assert(N == 0, "");
}
te
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_rangewhich is an alias ofinteger_rangefor the typestd::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
which reduces your test cases to the tiny amount of code they actually are.
This shorter formulation incidentally makes it more obvious that you didn't test any corner cases or types other than
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
The first problem is that our new test cases don't pass, because your
The second problem is that this implementation does not work for some obvious corner cases, such as
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
EDIT TO ADD a third "problem" I just thought of: The name
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 withtemplate
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 asnamespace 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.