14

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.

honk
  • 9,137
  • 11
  • 75
  • 83
Jay
  • 6,572
  • 3
  • 37
  • 65
  • 2
    Use the construct which provides the functionality you need. That is, use normal `for` loop (if that solves your problem easily). Don't force yourself doing complex things which need not to be complex. – Nawaz Jan 19 '14 at 11:16
  • 1
    use an STL algorithm with the preferred range. only fall back on a `for` loop if *none* of the algorithms fit. – Karoly Horvath Jan 19 '14 at 11:17
  • @Nawaz Right, but how about using `if (elem == container.front()) continue;` as a workaround? Not overly complex, is it? And we can still use the concise syntax. Still better than 3 lines of manual iterator handling as in pre-C++11 IMHO.. – Jay Jan 19 '14 at 11:21
  • 2
    @Jay: don't, unless you want to pray each time that the container doesn't contain equal(==) elements. – Karoly Horvath Jan 19 '14 at 11:22
  • @Jay: The correct way to do this is with iterators, not with indices (they aren't generic) or C++ algorithms (I don't believe any of them fit here). See Daniel's answer. – user541686 Jan 19 '14 at 11:23
  • @Jay Depending on the container and the condition for skipping, this could be unnecessarily expensive. It might also fail if there are duplicate elements in the container. – Joseph Mansfield Jan 19 '14 at 11:24
  • @KarolyHorvath True.. I was thinking about containers containing pointers (99% use case in our code base) – Jay Jan 19 '14 at 11:25
  • @Jay: Containers shouldn't contain raw pointers if they own them, because this isn't exception-safe and doesn't convey the semantics that the containers *own* the objects. Instead you should be using a pointer-container, like Boost's `ptr_vector`. Also if there's one thing you should learn with C++ is this: **don't be lazy**. I know iterators are painful, but they are the correct way to do this and will help you learn how to write robust generic code. Indices don't always make sense but iterators always do. – user541686 Jan 19 '14 at 11:26
  • @all Good discussion, thanks for all input and discussing various aspects! – Jay Jan 19 '14 at 13:24

8 Answers8

33

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.

Ali
  • 56,466
  • 29
  • 168
  • 265
  • 3
    Definitely prefer the branching outside the loop – Jay Jan 19 '14 at 13:23
  • One drawback to this is increased verbosity when you're getting the container from a function that may return an empty list, because then you end up with essentially two checks on container size – John Neuhaus Feb 25 '19 at 17:33
6

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.

Sam Kellett
  • 1,277
  • 12
  • 33
3

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++;
}
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Netwave
  • 40,134
  • 6
  • 50
  • 93
  • [Those should be pre-increments](https://stackoverflow.com/questions/21215947/skipping-in-range-based-for-based-on-index#comment77339338_21216043) – underscore_d Jul 19 '17 at 09:09
2

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++)
Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
  • Using post-increment here introduces pointless potential for overhead, especially for containers whose iterators are class types instead of pointers. I wish everyone could get out of the habit of defaulting to postinc/dec. – underscore_d Jul 19 '17 at 09:07
  • @underscore_d It's called C++ for a reason right :P Can't the compiler just notice that the value isn't being used though? – GeeTransit Mar 01 '20 at 02:57
  • @GeeTransit Yes, but it doesn't have to, especially with complex iterators that might have side-effects it doesn't analyse as being no-ops. And yes, it being called C++ is a tad unfortunate, but I doubt "++C" would've had the same ring to it, so here we are. ;-) – underscore_d Apr 24 '20 at 08:39
2

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; });
}
Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176
  • I get your point, though it's usually a bunch of things we do with our business objects so even lambdas could get biggish and out of hand. Still a problem with `for_each` when we need to skip handling for one of the elements, isn't it? – Jay Jan 19 '14 at 11:43
  • If the skipping is based on the index, you're probably doing something wrong. Restructure your data. – Karoly Horvath Jan 19 '14 at 11:57
0

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.

nurettin
  • 11,090
  • 5
  • 65
  • 85
0

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

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

honk
  • 9,137
  • 11
  • 75
  • 83