patterncppMajor
Simple integer range for C++11 range-based for loops
Viewed 0 times
simplerangeloopsforbasedinteger
Problem
I'm really tired of having to type
whenever I want to iterate over an integer range (most IDEs help with the typing, but still it looks so verbose, naming the integer 3 times!)
I wanted something like this:
Or if
My very simple implementation:
I named it
Of course this class could be generalized in many ways, but I think if you need more complex functionality (e.g. custom step sizes, double values), then you are doing something special and you are better off writing the explicit for loop.
What do you think about it?
If I use such a thing throughout my project, would it confuse and disturb/distract people too much compared to just using the classic and verbose
for (int iSomething = rangeBegin; iSomething < rangeEnd; ++iSomething)
{
...
}whenever I want to iterate over an integer range (most IDEs help with the typing, but still it looks so verbose, naming the integer 3 times!)
I wanted something like this:
for (int iSomething : LoopRange(rangeBegin, rangeEnd))
{
...
}Or if
rangeBegin is 0 (the majority of the cases) then a simplefor (int iSomething : LoopRange(rangeEnd))
{
...
}My very simple implementation:
class LoopRangeIterator
{
public:
LoopRangeIterator(int value_)
: value(value_){}
bool operator!=(LoopRangeIterator const& other) const
{
return value != other.value;
}
int const& operator*() const
{
return value;
}
LoopRangeIterator& operator++()
{
++value;
return *this;
}
private:
int value;
};
class LoopRange
{
public:
LoopRange(int from_, int to_)
: from(from_), to(to_){}
LoopRange(int to_)
: from(0), to(to_){}
LoopRangeIterator begin() const
{
return LoopRangeIterator(from);
}
LoopRangeIterator end() const
{
return LoopRangeIterator(to);
}
private:
int const from;
int const to;
};I named it
LoopRange to make it clear that it's for loops and it isn't some general integer range class that you would use for intersecting or building union etc.Of course this class could be generalized in many ways, but I think if you need more complex functionality (e.g. custom step sizes, double values), then you are doing something special and you are better off writing the explicit for loop.
What do you think about it?
If I use such a thing throughout my project, would it confuse and disturb/distract people too much compared to just using the classic and verbose
for(...; ...; ...) style?Solution
You can make the class template with trivial changes (add
That will even allow you to explicitly tell what kind of integer you want to loop with if needed:
If you need to generate indices to iterate through a
You can simplify some of your functions thanks to list initialization. For example, used in a
On a side note, such a
template and change int by T in your classes), then make a construction function that deduces integer types:template
LoopRange range(T from, T to)
{
static_assert(std::is_integral::value,
"range only accepts integral values");
return { from, to };
}That will even allow you to explicitly tell what kind of integer you want to loop with if needed:
for (auto i: range(0, 5))
{
std::cout << i << " ";
}If you need to generate indices to iterate through a
std::vector, this can be useful since std::vector::size_type is probably bigger than int. While the static_assert avoids some potential problems with floating point values, it also inhibits the use of integer-like classes (for example, a hypothetical BigNum class).You can simplify some of your functions thanks to list initialization. For example, used in a
return statement, it sallows you not to explicitly repeat the return type (unless the return type's constructor is explicit):LoopRangeIterator begin() const
{
return { from };
}
LoopRangeIterator end() const
{
return { to };
}On a side note, such a
range utility would also be interesting if it worked with floating point numbers, and maybe decimal numbers in the future (akin to Python's numpy.arange). However, you would have to special-case the class for those types if you want to avoid problems: if you repeatedly add the same floating point (say 0.01), you will accumulate rounding errors. Computing every value from the base value with a multiplication could be away to circumvent such a problem.Code Snippets
template<typename T>
LoopRange<T> range(T from, T to)
{
static_assert(std::is_integral<T>::value,
"range only accepts integral values");
return { from, to };
}for (auto i: range<unsigned>(0, 5))
{
std::cout << i << " ";
}LoopRangeIterator begin() const
{
return { from };
}
LoopRangeIterator end() const
{
return { to };
}Context
StackExchange Code Review Q#51523, answer score: 22
Revisions (0)
No revisions yet.