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
2  
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 '14 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 '14 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 '14 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 '14 at 14:49
1  
Just a note: boost::irange also takes a step parameter. –  Morwenn May 26 '14 at 9:50

5 Answers 5

up vote 7 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 '14 at 20:10
    
@isarandi yes, that would a useful wrapper (std::iota like behavior) –  TemplateRex Jun 1 '14 at 20:16
    
@isarandi updated with a thin wrapper that counts from 0. –  TemplateRex Jun 1 '14 at 20:25
2  
Just a warning: boost::irange interprets the step in a weird way. irange(0,3,2) yields only 0, while for example Python's range(0,3,2) yields both 0 and 2. The last number that boost yields is the largest x such that x+step<=end, while Python's condition is just x<end. I find the Python version more straightforward so I wrapped boost's irange to make it work like Python's. –  isarandi Jan 13 at 12:43

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 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 };
}
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 '14 at 14:54
    
@qorilla A little static_assert should do the trick :) –  Morwenn May 23 '14 at 14:55
    
While return { from }; is shorter to type, I don't think it is better than return LoopRangeIterator(from); from a readability and maintainability point of view. –  R Sahu Jan 3 at 3:23
    
@RSahu There have been long debates about that recently, even in the standard committee itself, so it's still debatabe. My point of view is that when you return from a one-liner, there is no way you could mean anything else and writing the return type a second time doesn't help much. –  Morwenn Jan 3 at 11:23

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 '14 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 '14 at 14:06

Preprocessor Macros! All of the loops below will produce the same result. Unfortunately, macros can't be defined more than once, so you have to give them different names.

#include <iostream>

#define irange(i,a,b) int i = (a); i < (b); ++i // if you want to use ints all the time
#define range(i,a,b) i = (a); i < (b); ++i      // if you ever want to use something other than an int
#define zrange(i,b) i = 0; i < (b); ++i       // if you want to start at zero
#define izrange(i,b) int i = 0; i < (b); ++i  // if you want to start at zero and use ints

int main ( )
{
    for ( int num = 0; num < 10; ++num ) std::cout << num << ' ';

    std::cout << '\n';

    for ( irange ( num, 0, 10 ) ) std::cout << num << ' ';

    std::cout << '\n';

    for ( int range ( num, 0, 10 ) ) std::cout << num << ' ';

    std::cout << '\n';

    for ( int zrange ( num, 10 ) ) std::cout << num << ' ';

    std::cout << '\n';

    for ( izrange ( num, 10 ) ) std::cout << num << ' ';

    std::cout << '\n';
}

You could also write more macros to do things like increment by something other than 1.

share|improve this answer
    
Well, this is not as clean as a modern C++ solution. For the reasons you mentioned and for all the other reasons we try to avoid macros :/ –  Morwenn Nov 6 '14 at 21:01
    
but I did it in one line. ;) –  Yay295 Nov 6 '14 at 21:04
1  
Unfortunately, in real code, we value the usage and quality of the implementation more than its character count :p –  Morwenn Nov 6 '14 at 21:20

obviously not better than yours but you may consider it as an alternative. By using std vector to get same result

#include <vector>
template <typename T>
std::vector<T> irange(const T& begin, const T& end)
{
    static_assert(std::is_integral<T>::value,
        "range only accepts integral values");
    std::vector<T> v;

    for (auto i = begin; i < end; ++i)
    {
        v.push_back(i);
    }

    return v;
}

or this which is worked fine with me, i tested in VC++ 2013

template <int begin_,int end_>
struct range {
    struct iterator {
        int value;
        iterator(int v) : value(v) {}
        operator int() const { return value; }
        operator int& ()      { return value; }
        int operator* () const { return value; }
    };
    iterator begin() { return begin_; }
    iterator end() { return end_; }
};

usage

for (auto i : range<0, 5>())
{
    std::cout << i << ' ';
}
share|improve this answer
2  
This is a heavy weight implementation compared to the OP's code. –  R Sahu Jan 3 at 5:17
    
@RSahu yes, i mentioned it as an alternative compared to OP's code –  MORTAL Jan 3 at 5:21
    
@RSahu .. i updated my answer i think it light and short suitable for its purpose –  MORTAL Jan 3 at 12:49

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.