36

I'm discovering C++11 range based loop and already love it. It makes you save lot of time when coding.

However, I'm used to writing some loops with extra statements/conditions and am wondering it this can be achieved when using C++11 range based-loop:

1. Extra incrementation

std::vector<int> v = { 1, 2, 3, 4, 5 };
size_t index = 0;
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end(); ++iter, ++index )
{
    std::cout << "v at index " << index << " is " << *iter;
}

Could become:

size_t index = 0;
for ( int val : v )
{
    std::cout << "v at index " << index << " is " << *iter;
    ++index;
}

However, incrementing index in the for loop is better because guaranteed (incremented even if for loop has continue statements for example)

Is there a way to move ++index inside the for statement?

2. Get iteration index dynamically

std::vector<int> v = { 1, 2, 3, 4, 5 };
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end(); ++iter )
{
    std::cout << "v at index " << ( iter - v.begin() ) << " is " << *iter;
}

Can something similar be achieved with C++11 range-based loop? Is there a way to know how many iterations were done so far?

3. Extra exit condition

I often use this in code where break is forbidden as a coding guidline:

std::vector<int> v = { 1, 2, 3, 4, 5 };
bool continueLoop = true;
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end() && continueLoop; ++iter )
{
    std::cout << "v value is " << *iter;
    if ( *iter == 4 )
        continueLoop = false;
}

Can something similar be achieved with C++11 range-based loop (break exeuction without using a break)?

15
  • 1
    Short answer is "no, you can't" - range based loops work best when all you want to do is simply iterate over the elements of a vector. If you want the loop iteration itself to do other things, there is always the old-fashioned for-loop style. Commented Apr 14, 2016 at 7:09
  • 13
    why do you want to "break exeuction without using a break"? break does exactly that Commented Apr 14, 2016 at 7:09
  • 2
    100% agree with Exceptyon - "do not use break in loops" is a silly rule, it just leads to more complex code that is harder to understand when you have several conditions that "exit early" - and especially if you for some reason don't want to nest a dozen if-else inside the loop. if (a) break; if (b) break; if (c) break; is much easier to read than if (a) cont = false; else if (b) cont = false; else if (c) cont = false; [because b and c should not be executed when a is true, so we NEED to use else-if to make sure that doesn't happen] Commented Apr 14, 2016 at 7:13
  • 1
    @Exceptyon: Unfortunately, you need to stick with your company coding guidlines. For instance MISRA 98 rule does not allow break statement to be used...I'm not using it, but there could be cases where breakis forbidden by company guidlines... Commented Apr 14, 2016 at 7:23
  • 1
    @Exceptyon: Apart from company guidelines, there are cases where one simply cannot do a break; the most obvious one is when inside a switch statement inside the loop. (To reuse the break keyword there is one of those truly stupid syntactic decisions in the design of C that C++ inherited.) Commented Apr 14, 2016 at 11:43

7 Answers 7

14

Unfortunately, you can't put the increment into the range based for loop. However, in your specific case - as std::vector stores its elements contigously in memory - you can simulate option 2 by falling back to pointers (thanks to @M.M and @Jarod42 for corrections and improvements):

for ( const int& val : v )  {
    std::cout << "v at index " << &val-v.data() << " is " << val; 
}

more generic:

for ( const auto& val : v )  {
    std::cout << "v at index " << std::addressof(val)-v.data() << " is " << val; 
}

The other thing you can do is to write a index_range class, that represents a collections of indexes over which you can iterate in your range based for loop:

struct index_range_it {
    size_t idx;
    size_t operator*(){
        return idx;
    }
    index_range_it& operator++() {
        idx++;
        return (*this);
    }
};

bool operator!=(index_range_it l,index_range_it r) {
    return l.idx != r.idx;
}

struct index_range {
    size_t size;
    index_range_it end(){return index_range_it{size};}
    index_range_it begin(){return index_range_it{0};}
};

int main()
{
    for (auto i: index_range{v.size()}){
        std::cout << "v at index " << i << " is " << v[i]; 
    }        
}

A full fledged implementation of this idea can be found e.g. here

Such a range can then also be composed to something, where the iterator returns a proxy object containing the index as well as a reference to the current object and with c++17's structured binding that would be even more convenient to use.

Sign up to request clarification or add additional context in comments.

5 Comments

Will this work even if v is not std::vector and has no guarantee, that elements are stored in order in memory?
@Zerges: Unfortunately it won't essentially, you are doing pointer arithmetic based on the asusmption of a continous layout in memory.
v.data() may be used instead of &*v.begin().
index_range_it is nice....even if in the end you write more code than using the old for statement....;-)
@jpo38: Inreal code, you would of course put it in a header, streamline it somewhat (e.g. so that you don't require to call size explicitly and choose shorter names), but I agree that the benefit is somewhat questionable. Personally, I find it more apealing for the eyes and simpler to type though. Here you can find a more full fleged implementation.
13

Take a look at range-v3 and cppitertools.

cppitertools provides a very convenient enumerate:

std::vector<int> v = { 1, 2, 3, 4, 5 };
for (auto&& e : enumerate(v))
{
    std::cout << "v at index " << e.index << " is " << e.element;
}

Range-v3 unfortunately has no enumerate, which makes me very sad, but you can compose your own using view::ints and view::zip*. Range-v3 has the big advantage that it is the basis for the proposed ranges in for standard library. The range composition allows to build clean abstractions.

Regarding your last example, I would argue that you should avoid a loop altogether if you need to reduce the complexity. Instead use an appropriate algorithm such as std::find_if, std::any_of that matches your task without you having to express control flow.

Comments

6

For a general container, you cannot get the index nor the iterator from a range-based loop. Instead you either have to keep a separate variable, or go back to the iterator loop.

The iterator look can be written a bit more simply since C++11:

for( auto iter = begin(v); iter != end(v); ++iter )

For the specific case of a vector you can do:

for ( auto& val : v )
{
    cout << "Index is " << (&val - &v[0]) << '\n';
}

which works because vectors use contiguous storage.

4 Comments

&v[0] doesn't work with types with evil overload&.
@Jarod42 nor would &val. I guess you could use std::addressof.
(iter - begin(v)) returns index of iterator for std::vector and other containers with aligned storage.
@AndreiR. iter - begin(v) works for any container with random access iterator, not necessarily contiguous storage (i assume that's what you meant instead of "aligned storage" which all containers have). But you can't use that in the range-based for loop because there is no iter.
4

Here is a little something that can do #2

#include <iterator>
#include <utility>
#include <type_traits>
#include <cstddef>

template<typename Range>
class RangeBasedAdaptor
{
    Range& range;
public:
    RangeBasedAdaptor(Range& r) : range(r) {}
    struct iterator;
    typedef typename std::remove_reference<decltype(*std::begin(range))>::type mapped_type;
    typedef decltype(std::begin(range)) underlying_iterator;

    struct value_type
    {
        std::size_t index() const { return idx; }
        mapped_type& value() { return *ui; }
        const mapped_type& value() const { return *ui; }
    private:
        std::size_t idx;
        underlying_iterator ui;
    friend
        struct iterator;
    };

    struct iterator
    {
        iterator();
        iterator& operator++() { ++val.ui; ++val.idx; return *this; }
        value_type& operator*() { return val; }
        bool operator!=(iterator other) { return val.ui != other.val.ui; }
    private:
        iterator( underlying_iterator ui, std::size_t idx ) { val.idx=idx; val.ui=ui; }
        value_type val;
    friend
        class RangeBasedAdaptor;
    };

    iterator begin() { return iterator{ std::begin(range), 0 }; }
    iterator end() { return iterator{ std::end(range), (std::size_t)-1 }; }
};

template<typename Range>
auto indexed(Range& r) -> RangeBasedAdaptor<Range>
{
    return {r};
}

// -------------------------------------------------------------------------------------

#include <iostream>
#include <vector>
#include <list>

int main()
{
    std::vector<int> foo = { 1,2,3,4,5,6 };

    for( auto& val : indexed(foo) )
    {
        val.value() += 3;
        std::cout << val.index() << " : " << val.value() << std::endl;
    }

    const std::list<float> foo2 = { 1.1f, 2.2f, 3.3f };

    for( auto& val : indexed(foo2) )
    {
        std::cout << val.index() << " : " << val.value() << std::endl;
    }
}

It is only intended with range based for loops, hence the minimal iterator.

2 Comments

Looks good. However, we end up writting more code than when not using range-based loops....;-)
not really, RangeBasedAdaptor only needs to be written once, works with all containers
3

In computer languages, traditionally a "for" loop is a loop with language specified looping conditions. If the programmer wants to specify their own looping conditions, they use a "while" loop. From this perspective, C++'s range-based for loops are the first time the language has really ever had a real "for" loop construct. So it may take a C++ programmer a bit to wrap their minds around the fact that if they can't deal with the compiler-generated loop conditions, they should be using a different construct.

That being said, since iterators can be custom objects, you could do anything you want with a range-based for loop by writing yourself a custom iterator. In the past, I've typically found this effort isn't worth the extra code, unless you are going to reuse that iterator multiple times.

1. Extra incrementation

However, incrementing index in the for loop is better because guaranteed (incremented even if for loop has continue statements for example)

Is there a way to move ++index inside the for statement?

Yes, with a custom iterator. However, that's a lot of work. This is easy:

for (auto element : container) {
   ++index;
}

Here we also know its guaranteed to get incremented, because its placed at the top before any possible break or continue statements.

  1. Get iteration index dynamically

Can something similar be achieved with C++11 range-based loop? Is there a way to know how many iterations were done so far?

Again, this could be done with a custom iterator, but almost certainly not worth it. I had to do this myself just last week, and the solution looked very much like the code in #1 above.

  1. Extra exit condition

I often use this in code where break is forbidden as a coding guidline:

This should never be in a coding guideline. Its flat out wrong. I'm not arguing for you to break your guidelines. But I am arguing for anyone reading this to never put any such thing into a coding guideline document ever again.

There's a common rule of thumb for good structured coding that any block of code should only have one exit point (aka: goto considered harmful). However, a loop with two exit statements still has only one exit point. Both exits return control to the same point outside the loop.

More practically, there are many types of loops that have to be way more complicated (eg: harder to understand and keep working properly) if you can't put your exit test in the middle of them. If a guideline routinely forces you to write more obtuse code, its a bad guideline.

Again, you could get around this with a custom iterator. In this case, I'd argue it may be the way to go. Sure, its tons more code than its worth just to work around your stupid coding guideline. But that's the guideline's fault, not yours.

Comments

2

Just for completeness, and regarding the first point (extra incrementation), starting in C++20 it will be possible to use additional initializations in range-for loops. For example:

std::vector<int> values = {2, 4, 6, 8};
for (size_t i = 0; auto v : values) {
  std::cout << "values[" << i << "] = " << v << '\n';
  ++i;
}

References:

4 Comments

It would be interesting if one could do for (size_t i = 0; auto v : values; i++) like a plain old for.
@sergiol probably the reason is that the overall shape of the for loop you mention is exactly the same as the old one: for( expression ; expression ; expression), making it impossible to distinguish one from another
it is not impossible: It has a construct which is type iterator : collection. Just looking at the : char should be enough to distinguish the range based from the old one.
I've just seen this nice article about initialization in for and other statements mariusbancila.ro/blog/2021/03/23/…
0

I won't write code that replaces a perfectly good break statement.

Getting the index for a vector (which is when it is useful) is easy: iterator over auto& x:v and then subtract std::addressof(x)-v.data().

Which leaves #1.

template<class It, class Operation>
struct iterator_with_extra_increment_t {
  using self=iterator_with_extra_increment_t;
  It it;
  Operation& op;
  void operator++(){ ++it; op(); }
  auto operator*()->decltype(*std::declval<It&>()) { return *it; }
  friend bool operator!=(self const& lhs, self const& rhs){
    return lhs.it != rhs.it;
  }
  friend bool operator==(self const& lhs, self const& rhs){
    return lhs.it == rhs.it;
  }
};
template<class It, class Operation>
iterator_with_extra_increment_t<It, Operation>
iterator_with_extra_increment( It it, Operation& operation ) {
  return {std::move(it), operation};
}
template<class Range, class Modify>
struct iterate_modified_t {
  Range r;
  Modify m;
  auto begin() { using std::begin; return m(begin(r)); }
  auto end() { using std::end; return m(end(r)); }
};
template<class Range, class Modify>
iterate_modified_t<Range, std::decay_t<Modify>>
iterate_modified( Range&& r, Modify&& m) {
  return {std::forward<Range>(r), std::forward<Modify>(m)};
}
template<class Range, class Op>
auto also_on_inc( Range&& r, Op&& op ) {
  auto modify = [op = std::forward<Op>(op)](auto&& it) {
    return iterator_with_extra_increment(decltype(it)(it), op);
  };
  return iterate_modified( std::forward<Range>(r), std::move(modify) );
}

now we have also_on_inc:

std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : also_on_inc(a, [&]{++count;}) ) {
  std::cout << count << "->" << x << '\n';
}

live example.

Some of the above code is C++14 because I am too lazy to write out the ->decltype clauses.

We can improve that syntax with operator abuse to something like:

std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : a *also_on_inc* [&]{++count;} ) {
  std::cout << count << "->" << x << '\n';
}    

if we are insane, which lets us do

std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : a *also_on_inc* [&]{++count;} *also_on_inc* [&]{std::cout << count << '\n';} ) {
  std::cout << count << "->" << x << '\n';
}

easier chaining of such clauses.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.