Filip S.
Filip S.

Reputation: 1544

Have range-based for loop start at point in vector

I have a std::vector<MyClass*> container of pointers to objects of some class, and want to iterate over the vector using a range-based for loop, like this

for (MyClass *item : container)
{
    // Do stuff, not changing the container
}

Inside this loop I want to loop through the container one more time, but starting at the next element in the container. I couldn't really find a way of doing this without iterators, so my solution was to just use those instead

for (auto item1 = container.begin(); item1 != container.end(); ++item1)
{
    for (auto item2 = item1.next(); item2 != container.end(); ++item2)
    {
        // Do stuff, not changing the container
    }
}

My question is: is there any way of doing this, without having to resort to iterators? I realize range-based for loops are just syntactical sugar, and really uses iterators, but I like my syntactical sugar!

Upvotes: 1

Views: 5489

Answers (4)

sellibitze
sellibitze

Reputation: 28097

(Please note the edit below)

The for-range loop does not give you access to the internal iterator. On one hand, this is good because you can't mess with it. On the other hand this might be bad because you need the iterator to know where you are in the container. So, we can't use a for-range loop as outer loop unless you iterate over something with a linear memory layout where you can take the address of an item as an iterator.

Also, the standard library is not yet up to the task of dealing with ranges in a convenient way. I'm using some utilities from Boost like iterator_range for this code snippet:

using boost::make_iterator_range;
using std::next;
for (auto iter1 = container.begin(), ee = container.end(); iter1 != ee; ++iter1) {
    auto& item1 = *iter1;
    for (auto& item2 : make_iterator_range(next(iter1),ee)) {
        // do something with item1 and item2
    }
}

Admittedly, not very pretty. But this shows one way how you can use the for-range loop given a pair of iterators. Basically, the for-range loop eats anything that offers begin/end functions. make_iterator_range wraps a pair of iterators and returns something, that provides begin/end functions.

Edit: I recently learned that boost::irange can also produce sequences of iterators (and not just sequences of numbers). With that in mind, check out this program:

#include <iostream>
#include <vector>
#include <boost/range/irange.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/adaptor/indexed.hpp>

int main()
{
    using std::vector;
    using std::next;
    using boost::irange;
    using boost::make_iterator_range;
    namespace ba = boost::adaptors;

    vector<double> x {1.1, 2.2, 3.3, 4.4, 5.5, 6.6};

    for (auto iter1 : irange(begin(x),end(x))) {
        auto& item1 = *iter1;
        for (auto& item2 : make_iterator_range(next(iter1),end(x))) {
            // do something with item1 and item2
            std::cout << item1 << " < " << item2 << std::endl;
        }
    }

    return 0;
}

I tested this with G++ 4.6.3 in C++0x mode and Boost 1.48 and it actually works.

Upvotes: 3

eerorika
eerorika

Reputation: 238351

Added my comment as an answer by request from OP.

You can't do that without iterators. But you can avoid giving up the syntactic sugar for iterating the entire collection, see this answer. I haven't tested that code, but a comment to that answer suggests boost::counting_range, which might be even better.

Upvotes: 2

green lantern
green lantern

Reputation: 1095

You could create a simple class that will access the range:

#include <vector>
#include <iostream>

template<typename I>
struct Sub {
    I _begin;
    I _end;

    Sub(I b, I e): _begin(b), _end(e) {}

    I begin() const { return _begin; }
    I end() const { return _end; }
};

template<typename I>
Sub<I> sub(I b, I e) { return Sub<I>(b, e); }

int main() {
    std::vector<int> a{0,1,2,3,4,5,6,7,8,9};

    for (auto i: sub(a.begin() + 3, a.end() - 1)) {
        std::cout << i << "\n";
    }
}

This code prints:

3
4
5
6
7
8

Upvotes: 2

BЈовић
BЈовић

Reputation: 64223

The range for loop iterates all elements in the container. That means that you can change that loop to :

for (auto item1 : container )
{
    for (auto item2 = item1.next(); item2 != container.end(); ++item2)
    {
        // Do stuff, not changing the container
    }
}

Since the inner loop doesn't iterate whole container, you have to use normal for loop.

Upvotes: 0

Related Questions