Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I'm really tired of having to type

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 simple

for (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?

share|improve this question
1  
Take a look at my implementation at klmr/cpp11-range. Needless to say, I think my implementation has a few good ideas which set it apart from other implementations (the README explains these). –  Konrad Rudolph May 23 at 14:27
    
You shouldn't fear to add a step argument. It is common enough that Python's range built-in functions provides it. –  Morwenn May 23 at 14:34
    
@Morwenn Python, unlike C++, has named arguments. I feel that without name it would be un-intuitive enough to warrant this special treatment. Furthermore, the step argument creates a separate type which makes optimisation somewhat easier for the compiler (it can use ++ instead of += var for the simple case). –  Konrad Rudolph May 23 at 14:47
    
@KonradRudolph You are probably right. I've never seen anybody explicitely name this argument though (some people probably do, but it seems that the vast majority doesn't). –  Morwenn May 23 at 14:49
1  
Just a note: boost::irange also takes a step parameter. –  Morwenn May 26 at 9:50
show 2 more comments

3 Answers 3

up vote 3 down vote accepted

Why write it yourself if you can use Boost.Range's irange. You can even adapt this to set the starting index to 0 and get std::iota type behavior (called iota_n here).

#include <boost/range/irange.hpp>
#include <iostream>

template<class Integer>
decltype(auto) iota_n(Integer last)
{
    return boost::irange(0, last);    
}

template<class Integer, class StepSize>
decltype(auto) iota_n(Integer last, StepSize step_size)
{
    return boost::irange(0, last, step_size);    
}

int main()
{
    for (auto x : iota_n(5)) // 01234
        std::cout << x;
}

Live Example, using Clang 3.4 return-type-deduction in C++1y mode (also supported by gcc 4.9, and other compilers soon (use trailing -> decltype(/*statement inside function*/) return types for C++11 compilers)

share|improve this answer
    
Yes, I looked into it and chose this. But I also wrote a wrapper function that takes a single parameter (to) and sets the beginning to zero, as in python. –  isarandi Jun 1 at 20:10
    
@isarandi yes, that would a useful wrapper (std::iota like behavior) –  TemplateRex Jun 1 at 20:16
    
@isarandi updated with a thin wrapper that counts from 0. –  TemplateRex Jun 1 at 20:25
add comment

If you have operator!=, you should also have operator== for symmetry:

bool operator==(LoopRangeIterator const& other) const
{
    return value == other.value;
}

In addition, it's more common to overload operator!= in terms of ==:

bool operator!=(LoopRangeIterator const& other) const
{
    return !(value == other.value);
}
share|improve this answer
    
Makes sense in general. Here I concentrated on the functionality needed in the for loop as I intend this class to be used nowhere else. –  isarandi May 23 at 14:03
    
@qorilla: That's fine. I would still follow this rule, at least with operator-overloading. It may also seem a bit tacky when you have one overload but not the other. –  Jamal May 23 at 14:06
add comment

You can make the class template with trivial changes (add template<typename T> and change int by T in your classes), then make a construction function that deduces integer types:

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 };
}

That will even allow you to explicitly tell what kind of integer you want to loop with if needed:

for (auto i: range<unsigned>(0, 5))
{
    std::cout << i << " ";
}

If you need to generate indices to iterate through a std::vector, this can be useful since std::vector<T>::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 disallows you to explicitly repeat the return type (unless the return type's constructor is explicit):

LoopRangeIterator begin() const
{
    return { from };
}

LoopRangeIterator end() const
{
    return { to };
}
share|improve this answer
    
The problem with the template solution is that floating point numbers would need more care. If you repeatedly add an increment to a floating point number it may never hit the expected "end" value. So I'd need some tricks (e.g. keeping track of the number of remaining iterations). It would be too much complication for a rare use case. I could probably restrict template instantiation to integral types with some template magic but why overcomplicate things? –  isarandi May 23 at 14:54
    
@qorilla A little static_assert should do the trick :) –  Morwenn May 23 at 14:55
    
Wouldn't it be "std::vector<T>::size_type is probably bigger than int"? –  jliv902 May 23 at 16:04
    
@jliv902 You're right, what I wrote doesn't make sense. Thanks :) –  Morwenn May 24 at 14:01
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.