patterncppMinor
Template integer range, version 2
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
Here is an example of how this template integer range can be used:
I accept any kind of review, be it about style, correctness or possible improvements to the utility in general :)
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:
is the sequence
given that it sure looks like we're going from
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
First, let's determine the size of our sequence. For signed integers, we can just divide by
And then, with a default template parameter, our
Add Default Step
Using
Add Default Start
You could also provide a default beginning by having your aliases take a pack of ints. Something like:
With:
This would allow:
YMMV on whether or not you want to allow this, but just throwing this out as an option in case you do.
Principle of Least Surprise
I was pretty surprised that:
make_index_rangeis the sequence
std::index_sequencegiven 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.