Range-Limited Range-For Loops

There’s one feature of C++11 that I haven’t been making good use of, and that’s the range-based for loop.  The problem I’ve found with using that is it iterates through an entire container or array (or initializer_list). And to be honest – I sometimes use ‘old’ compilers like Microsoft’s Visual C++ 2010, which doesn’t support it. Plus the 2012 version has issues using range-based for with initializer_lists (which require the Nov 2012 CTP compiler preview – a buggy alpha build).

But a recent remark I saw the other day about using a range limit support function with range-for got me to thinking – maybe I could and probably should start to use it more.

For a simple example of where I could have been using them, you can just look at the code from the last blog entry, and see how it can easily be transformed:

  // Regular for-loop version:
  for (auto it = il.begin(); it != il.end(); ++it)
     *inserter++ = *it;

  // Range-based for loop version:
  for (const auto& elem : il)
     *inserter++ = elem;

The clean minimalistic syntax of range-for is nice, and the fact it saves typing is even better. (Note that most references recommend you use auto & or const auto & to avoid copying and to maintain const-ness where needed.)

Range-for is all good and well, until you find you need to work on elements x through y, and then its back to using the old for-loop syntax.  Unless, that is, we find a workaround. And as always, templates and helper functions can alleviate some of that problem.  What we want now is a range-for that looks like this:

  for(auto &elem : range(first, last))
     elem = val;

That has a nice simple syntax, and actually makes it pretty clear what we are working on, no?  The method of achieving that result actually took me maybe 15 minutes to work out and code, because the rules for what range-based for works on are rather nice.  If it’s not using an initializer_list or an array, a range-for loop must take an object that has both begin() and end() member functions – or an object on which the std::begin() and std::end() global functions could be used.  With that bare minimum requirement, all that is needed then is a simple interim object class:

template <typename Iter>
class IterRange
   IterRange();    // prevent this class from being constructed without any parameters
   Iter _beg, _end;
   IterRange(Iter beg_, Iter end_) : _beg(beg_), _end(end_) {}

   // The necessary functions:
   Iter begin() { return _beg; }
   Iter end() { return _end; }

The above class only holds 2 members, the beginning and end of the range, and for the most part, acts as a struct, where the data is assigned and returned in the same way.  Both copy and move constructors and assignment operators are unneeded, as the default behavior of the compiler is adequate. The default empty constructor, however, is made impossible to use.  We don’t want an object with undefined members, and in fact the iterators types are required to actually implement the class.. so maybe its not necessary at all?  Meh, I prefer to err on the side of caution.

The only thing remaining now would be that ‘range()’ function.  Well, given the class, wouldn’t you expect it to be quite simple?  Turns out it is:

template <typename Iter>
IterRange<Iter> range(Iter beg, Iter end)
   return IterRange<Iter>(beg, end);

With the above function and class object, range-limited range-based for loops become a reality.  And in fact, quite intuitive. Probably the best part of this is that compilers optimize out both the class object and the function when used as part of a range-for loop (as tested with G++ and MSVC).

One of the perks of working with C++ is that you learn more about the language while creating workarounds to perceived hurdles to productivity.  And, I suppose, that’s one of the drawbacks too – we waste time adding things that maybe should be part of the language standard?  Then again, we know that the C++ standard is actively evolving, and some things they didn’t get to implement in C++11 will probably see the light of day in the next standard release..

Hm, I don’t think I need to provide a full working example for this post, do I?  It’s a simple matter of plugging in range(start, end) on the right-hand side of a range-for loop. So, I’ll leave it at that for now =)