Question

Is there a way to access the iterator (I suppose there's no loop index?) in a C++11 range-based for loop?

Often we need to do something special with the first element of a container and iterate over the remaining elements. So I'm looking for something like the c++11_get_index_of statement in this pseudo-code:

for (auto& elem: container) 
{
  if (c++11_get_index_of(elem) == 0)
     continue;

  // do something with remaining elements
}

I'd really like to avoid going back to old-style manual iterator handling code in that scenario.

Was it helpful?

Solution

Often we need to do something special with the first element of a container and iterate over the remaining elements.

I am surprised to see that nobody has proposed this solution so far:

  auto it = std::begin(container);

  // do your special stuff here with the first element

  ++it;

  for (auto end=std::end(container); it!=end; ++it) {

      // Note that there is no branch inside the loop!

      // iterate over the rest of the container
  }

It has the big advantage that the branch is moved out of the loop. It makes the loop much simpler and perhaps the compiler can also optimize it better.

If you insist on the range-based for loop, maybe the simplest way to do it is this (there are other, uglier ways):

std::size_t index = 0;

for (auto& elem : container) {

  // skip the first element
  if (index++ == 0) {
     continue;
  }

  // iterate over the rest of the container
}

However, I would seriously move the branch out of the loop if all you need is to skip the first element.

OTHER TIPS

Boost provides a nice succinct way to do this:

std::vector<int> xs{ 1, 2, 3, 4, 5 };
for (const auto &x : boost::make_iterator_range(xs.begin() + 1, xs.end())) {
  std::cout << x << " ";
}
// Prints: 2 3 4 5

You can find make_iterator_range in the boost/range/iterator_range.hpp header.

How about using a simple for loop with iteratos:

for(auto it = container.begin(); it != container.end(); it++)
{
    if(it == container.begin())
    {
        //do stuff for first
    }
    else
    {
        //do default stuff
    }
}

It's not range based, but it's functional. In case you may still want to use the range loop:

int counter = 0;
for(auto &data: container)
{
    if(counter == 0)
    {
        //do stuff for first
    }
    else
    {
        //do default stuff
    }
    counter++;
}

No, you can't get the iterator in a range-based for loop (without looking up the element in the container, of course). The iterator is defined by the standard as being named __begin but this is for exposition only. If you need the iterator, it is intended that you use the normal for loop. The reason range-based for loop exists is for those cases where you do not need to care about handling the iteration yourself.

With auto and std::begin and std::end, your for loop should still be very simple:

for (auto it = std::begin(container); it != std::end(container); it++)

When iterating over elements, always prefer to use an algorithm, and use a plain for loop only if none of the algorithms fit.

Picking the right algorithm depends on what you want to do with the elements... which you haven't told us.

If you want to skip the first element, dump example:

if (!container.empty()) {
   for_each(++container.begin(), container.end(), [](int val) { cout << val; });
}

There is no way of knowing how far an element is within the container without having an iterator, pointer or an intrusive index. Here's a simple way of doing it:

int index= 0;
for (auto& elem: container) 
{
  if (index++ == something)
     continue;

  // do something with remaining elements
}

If you want to skip the first element, another way is to use a std::deque and pop_front the first element. Then you can do your ranged for loop with the container as usual.

When I need to do something like this on a random access container, my habit is to iterate over the indexes.

for( std::size_t i : indexes( container ) ) {
  if (i==0) continue;
  auto&& e = container[i];
  // code
}

the only tricky part is writing indexes, which returns a range of what boost calls counting iterators. Creating a basic iterable range from iterators is easy: either use boost's range concept, or roll your own.

A basic range for an arbitrary iterator type is:

template<typename Iterator>
struct Range {
  Iterator b; Iterator e;
  Range( Iterator b_, Iterator e_ ):b(b_), e(e_) {};
  Iterator begin() const { return b; }
  Iterator end() const { return e; }
};

which you can gussy up a bunch, but that is the core.

I would try to avoid using iterators, because the idea of a range-based for loop is to get rid of them. As of C++20, to skip the first element in your container, I would take one of the following approaches. I also include, for the sake of completeness, how to handle the first element separately:

  1. Handling the first element outside the loop

    You can use container.front() which exists for all sequence containers to access the first element. However, you must make sure that the container is not empty to avoid a segmentation fault. Then, to skip the first element (or more) in the loop, you can use the range adapter std::views::drop from the Ranges library. All together it looks as follows:

    std::vector<int> container { 1, 2, 3 };
    
    if(!container.empty()) {
        // do something with first element
        std::cout << "First element: " << container.front() << std::endl;
    }
    
    for (auto& elem : container | std::views::drop(1)) {
        // do something with remaining elements
        std::cout << "Remaining element: " << elem << std::endl;
    }
    

    Instead of container.front() you can also use another range-based for loop together with the range adapter std::views::take(1). The advantage of take() and drop() is that they work safely even if their arguments exceed the count of elements in your container.

  2. Handling the first element inside the loop

    You can use an init-statement in a range-based for loop to define a Boolean flag (or even a counter). This way, the flag is visible only within the scope of the loop. You can use the flag inside the loop as follows:

    std::vector<int> container { 1, 2, 3 };
    
    for(bool isFirst(true); auto& elem : container) {
        if(isFirst) {
            // do something with first element
            std::cout << "First element: " << elem << std::endl;
            isFirst = false;
            continue;
        }
    
        // do something with remaining elements
        std::cout << "Remaining element: " << elem << std::endl;
    }
    

Output for both approaches shown:

First element: 1
Remaining element: 2
Remaining element: 3

Code on Wandbox

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top