Is there a range class in C++11 for use with range based for loops?
Asked Answered
O

8

114

I found myself writing this just a bit ago:

template <long int T_begin, long int T_end>
class range_class {
 public:
   class iterator {
      friend class range_class;
    public:
      long int operator *() const { return i_; }
      const iterator &operator ++() { ++i_; return *this; }
      iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }

      bool operator ==(const iterator &other) const { return i_ == other.i_; }
      bool operator !=(const iterator &other) const { return i_ != other.i_; }

    protected:
      iterator(long int start) : i_ (start) { }

    private:
      unsigned long i_;
   };

   iterator begin() const { return iterator(T_begin); }
   iterator end() const { return iterator(T_end); }
};

template <long int T_begin, long int T_end>
const range_class<T_begin, T_end>
range()
{
   return range_class<T_begin, T_end>();
}

And this allows me to write things like this:

for (auto i: range<0, 10>()) {
    // stuff with i
}

Now, I know what I wrote is maybe not the best code. And maybe there's a way to make it more flexible and useful. But it seems to me like something like this should've been made part of the standard.

So is it? Was some sort of new library added for iterators over a range of integers, or maybe a generic range of computed scalar values?

Orosco answered 25/8, 2011 at 5:12 Comment(12)
+1. I would like to have such classes in my utilities. :-)Kostival
By the way, what is the point of writing range template function? It doesn't add anything to the usage in which range_class is used. I mean, range<0,10>() and range_class<0,10>() look exactly same!Kostival
@Nawaz: Yeah, you're right. I had some odd vision that I could make the function handle differentiating between the dynamic and static case, but I don't think it can be done.Orosco
@iammilind: It will work. :-). and I provided another solution. check it out.Kostival
@iammilind: Nawaz asked the same question 35 mins ahead of you ;)Execratory
@phresnel; Din't notice it. Deleting my question.Mangle
CUDA Thrust has a couple of such "value-generating" iterators. Agreed, it'd be nice to have them in the standard! Don't forget to add iterator tags and all that.Devoe
I don't see what one would gain over the regular for loop.Pastiness
@Gene Bushuyev: In a regular for loop you actually have to analyze the loop carefully to make sure the index value isn't changed in the loop, and the ends of the range it covers are not quite so explicit.Orosco
@Orosco -- I haven't met a professional developer who had difficulties or complained about such trivialities. Both for loop and for_each algorithm suit developer needs perfectly well. I actually don't think range-based for statement was a good idea to bring into language, as it creates an unholy dependence of language features on library, no wonder immediately issues of header files and ADL appeared.Pastiness
@Gene Bushuyev - Well, I think the way for loops currently work is absolute garbage, especially for STL classes. And you may think they are easy to understand, but when I see people modifying the index variable inside a loop, they become a nightmare for me. And that kind of code happens more often than you'd think.Orosco
To be pedantic I think this implementation has a bug, which is that you can't use it to iterate over the entire integer range. If you plug in INT_MIN and INT_MAX as your template arguments, INT_MAX when incremented will overflow give INT_MIN and cause infinite loops. "end" in the STL is supposed to be "one past the end" which can't fit inside the integer type itself, so I don't know that this can actually be implemented efficiently for the widest integer type on a given platform. For smaller integer types you can always make it use a wider type internally...Tannatannage
A
69

The C++ standard library does not have one, but Boost.Range has boost::counting_range, which certainly qualifies. You could also use boost::irange, which is a bit more focused in scope.

C++20's range library will allow you to do this via view::iota(start, end).

Ara answered 25/8, 2011 at 6:9 Comment(8)
Yes, that's definitely the nature of what I'd be looking for. I'm glad Boost did it. I'm sad the standard committee did not include it for whatever reason. It would've been a great complement to the range-base-for feature.Orosco
This answer better answers my direct question, and so I will choose it, even though Nawaz's answer is very good.Orosco
There has been much progress lately to get ranges into the standard (N4128). See github.com/ericniebler/range-v3 for a proposal and reference implementation.Blanc
@Ela782: ... and yet it seems we won't see it in C++17, right?Zenda
@einpoklum: No, it looks like it definitely won't make it. However it seems to be on a good path.Blanc
@Blanc en.cppreference.com/w/cpp/experimental/ranges/range/RangeFructose
@Andreas Yes, ranges made it into a TS a while ago, but I don't think there is/was ever a reference implementation that made it into the major compilers under the std::experimental::ranges namespace. range-v3 was always sort-of the reference implementation I'd say. But now I believe the basic range stuff has also recently been voted into C++20, so we will indeed get it in std:: soon! :-)Blanc
this is a documentation page for the proposed view::iota en.cppreference.com/w/cpp/ranges/iota_viewCafeteria
K
51

As far as I know, there is no such class in C++11.

Anyway, I tried to improve your implementation. I made it non-template, as I don't see any advantage in making it template. On the contrary, it has one major disadvantage : that you cannot create the range at runtime, as you need to know the template arguments at compile time itself.

//your version
auto x = range<m,n>(); //m and n must be known at compile time

//my version
auto x = range(m,n);  //m and n may be known at runtime as well!

Here is the code:

class range {
 public:
   class iterator {
      friend class range;
    public:
      long int operator *() const { return i_; }
      const iterator &operator ++() { ++i_; return *this; }
      iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }

      bool operator ==(const iterator &other) const { return i_ == other.i_; }
      bool operator !=(const iterator &other) const { return i_ != other.i_; }

    protected:
      iterator(long int start) : i_ (start) { }

    private:
      unsigned long i_;
   };

   iterator begin() const { return begin_; }
   iterator end() const { return end_; }
   range(long int  begin, long int end) : begin_(begin), end_(end) {}
private:
   iterator begin_;
   iterator end_;
};

Test code:

int main() {
      int m, n;
      std::istringstream in("10 20");
      if ( in >> m >> n ) //using in, because std::cin cannot be used at coliru.
      {
        if ( m > n ) std::swap(m,n); 
        for (auto i : range(m,n)) 
        {
             std::cout << i << " ";
        }
      }
      else 
        std::cout <<"invalid input";
}

Output:

10 11 12 13 14 15 16 17 18 19

Onine demo.

Kostival answered 25/8, 2011 at 5:55 Comment(16)
I like it. I thought about a non-template version. And I suppose a good compiler would optimize it well in the case when the values actually are constant. I'll have to test that out.Orosco
@Nawaz: I'd still template it, on the integral type :) I'd also propose to alias iterator to const_iterator, have iterator derive from std::iterator and have range implement cbegin and cend. Oh and... why does iterator::operator++ returns a const reference ?Lucylud
+1, nice solution; however, you should have retain the for loop version of the OP only in your test code also. :)Mangle
@Matthieu: There are lots of improvement can be done to this solution, beside what you proposed, it should have step parameter also (whose default value would be 1), and many others (design-wise).Kostival
@iammilind: I mentioned that gcc-4.5.1 have not implemented range-based for loop, that is why I used regular for loop, for ideone-demo purpose.Kostival
Why does it iterate from 0 to 9 if you told it to iterate from 0 to 10? Or is it meant to be startnumber, count (then the name of the variables in the constructor is not really reflecting its use)?Kestrel
@RedX: Its [m,n) . That is, it doesn't include the upper-limit!Kostival
@Kestrel - It's standard in both Python and C++ for a range to be treated as [begin, end). That is, not include the last value mentioned in the range specification.Orosco
@RedX: Dijkstra has a good write-up on why range labeling is best as [begin, end). @OP: +1 for the pun on range-based loops that isn't a pun :-)Devoe
@Kerrek: That is awesome article which presents a very strong argument in fact. Thanks for sharing it. :-)Kostival
This answer is very good, and I like the comment discussion under it (particularly the link the the article by Dijkstra). But @Nicol Bolas answers my direct question better.Orosco
@Omnifarious: I like the template version more also, because here the compiler cannot optimize it even for constants if the implementation is in a separate library.Bhutan
The advantage of the non-template version is that the lengths of your loops do not need to be known at compile time. You could make the integer type templated, of course.Ephesians
@Nawaz: Could you please explain why you return a copy in iterator operator ++(int) and for what it is necessary?Delectable
@weeska: That overload is supposed to implement postfix increment v++ which is supposed to return the value before the increment operation took place. I'd advise you to explore the difference between ++i and i++ where i is declared to be int.Kostival
@Nawaz: Thanks - I did not know that post increment operators where overloaded this way. Found this by googling: cs.northwestern.edu/~riesbeck/programming/c++/…Delectable
C
13

I wrote a library called range for exactly the same purpose except it is a run-time range, and the idea in my case came from Python. I considered a compile-time version, but in my humble opinion there is no real advantage to gain out the compile-time version. You can find the library on bitbucket, and it is under Boost License: Range. It is a one-header library, compatible with C++03 and works like charm with range-based for loops in C++11 :)

Features:

  • A true random access container with all the bells and whistles!

  • Ranges can be compared lexicographically.

  • Two functions exist(returns bool), and find(returns iterator) to check the existence of a number.

  • The library is unit-tested using CATCH.

  • Examples of basic usage, working with standard containers, working with standard algorithms and working with range based for loops.

Here is a one-minute introduction. Finally, I welcome any suggestion about this tiny library.

Cornaceous answered 28/8, 2011 at 8:47 Comment(4)
The one-minute introduction says that I don't have access to the Wiki. You need to make your wiki public.Ara
@Nicol Bolas I am really sorry, it is public now :)Cornaceous
Thank you for this, it's amazing. I feel like more people should know about it.Phantasy
The library no longer exists or at least not exists in the provided linkPerichondrium
J
3

I found that boost::irange was much slower than the canonical integer loop. So I settled on the following much simpler solution using a preprocessor macro:

#define RANGE(a, b) unsigned a=0; a<b; a++

Then you can loop like this:

for(RANGE(i, n)) {
    // code here
}

This range automatically starts from zero. It could be easily extended to start from a given number.

Julius answered 11/8, 2014 at 10:49 Comment(2)
Notice that for (RANGE(i, flag? n1: n2)) will yield surprising results, because you failed to follow one of the Basic Rules of Non-Evil Macros, which is to parenthesize all your parameters (including, in this case, b). Your approach also doesn't provide any performance benefit over the non-macro, "range object"–based approach (e.g. Nawaz's answer).Propylaeum
I'd personally do #define RANGE(t, v, s, e) (t v = s; v < e; ++v) to be used as for RANGE(unsigned, i, 0, n) { ....Cambodia
M
2

Here is a simpler form which is working nicely for me. Are there any risks in my approach?

r_iterator is a type which behaves, as much as possible, like a long int. Therefore many operators such as == and ++, simply pass through to the long int. I 'expose' the underlying long int via the operator long int and operator long int & conversions.

#include <iostream>
using namespace std;

struct r_iterator {
        long int value;
        r_iterator(long int _v) : value(_v) {}
        operator long int () const { return value; }
        operator long int& ()      { return value; }
        long int operator* () const { return value; }
};
template <long int _begin, long int _end>
struct range {
        static r_iterator begin() {return _begin;}
        static r_iterator end  () {return _end;}
};
int main() {
        for(auto i: range<0,10>()) { cout << i << endl; }
        return 0;
}

(Edit: - we can make the methods of range static instead of const.)

Middleton answered 20/12, 2011 at 15:50 Comment(0)
M
1

This might be a little late but I just saw this question and I've been using this class for a while now :

#include <iostream>
#include <utility>
#include <stdexcept>

template<typename T, bool reverse = false> struct Range final {
    struct Iterator final{
        T value;
        Iterator(const T & v) : value(v) {}
        const Iterator & operator++() { reverse ? --value : ++value; return *this; }
        bool operator!=(const Iterator & o) { return o.value != value; }
        T operator*() const { return value; }
    };
    T begin_, end_;
    Range(const T & b, const T & e)  : begin_(b), end_(e) {
        if(b > e) throw std::out_of_range("begin > end");
    }

    Iterator begin() const { return reverse ? end_ -1 : begin_; }
    Iterator end() const { return reverse ? begin_ - 1: end_; }

    Range() = delete;
    Range(const Range &) = delete;
};

using UIntRange = Range<unsigned, false>;
using RUIntRange = Range<unsigned, true>;

Usage :

int main() {
    std::cout << "Reverse : ";
    for(auto i : RUIntRange(0, 10)) std::cout << i << ' ';
    std::cout << std::endl << "Normal : ";
    for(auto i : UIntRange(0u, 10u)) std::cout << i << ' ';
    std::cout << std::endl;
}
Mireille answered 11/1, 2014 at 22:43 Comment(0)
P
-1

have you tried using

template <class InputIterator, class Function>
   Function for_each (InputIterator first, InputIterator last, Function f);

Most of the time fits the bill.

E.g.

template<class T> void printInt(T i) {cout<<i<<endl;}
void test()
{
 int arr[] = {1,5,7};
 vector v(arr,arr+3);

 for_each(v.begin(),v.end(),printInt);

}

Note that printInt can OFC be replaced with a lambda in C++0x. Also one more small variation of this usage could be (strictly for random_iterator)

 for_each(v.begin()+5,v.begin()+10,printInt);

For Fwd only iterator

 for_each(advance(v.begin(),5),advance(v.begin(),10),printInt);
Pitchblack answered 25/8, 2011 at 5:56 Comment(3)
How would you use this? I'm guessing you'd use a lambda for the function, but I'm not sure.Orosco
Would tell ya, but you will have accept the answer if you think it the right way to use it. :P Kidding. Posted the example already.Pitchblack
You can use a lambda here so auto range = myMultiMap.equal_range( key ); for_each( range.first, range.second, [&]( decltype( *range.first ) const& item ) { // code goes here } );Ephesians
R
-3

You can easily generate an increasing sequence in C++11 using std::iota():

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

template<typename T>
std::vector<T> range(T start, T end)
{
  std::vector<T> r(end+1-start, T(0));
  std::iota(r.begin(), r.end(), T(start));//increasing sequence
  return r;
}

int main(int argc, const char * argv[])
{
  for(auto i:range<int>(-3,5))
    std::cout<<i<<std::endl;

  return 0;
}
Reld answered 25/1, 2016 at 22:42 Comment(2)
The range class shall model the range. However you are literally constructing it. That is a waste of memory and memory accesses. The solution is highly redundant, because the vector holds no real information except for the number of elements and the value of first element (if it exists).Emboly
Yes, this is very inefficient.Orosco

© 2022 - 2024 — McMap. All rights reserved.