views:

145

answers:

3

This is a question that goes to how BOOST_FOREACH checks it's loop termination

cout << "Testing BOOST_FOREACH" << endl;
vector<int> numbers; numbers.reserve(8);
numbers.push_back(1); numbers.push_back(2); numbers.push_back(3);
cout << "capacity = " << numbers.capacity() << endl;
BOOST_FOREACH(int elem, numbers)
{
    cout << elem << endl;
    if (elem == 2) numbers.push_back(4); 
}
cout << "capacity = " << numbers.capacity() << endl;

gives the output

Testing BOOST_FOREACH
capacity = 8
1
2
3
capacity = 8

But what about the number 4 which was inserted half way through the loop? If I change the type to a list the newly inserted number will be iterated over. The vector push_back operation will invalidate any pointers IF a reallocation is required, however that is not happening in this example. So the question I guess is why does the end() iterator appear to only be evaluated once (before the loop) when using vector but has a more dynamic evaluation when using a list?

+2  A: 

Under the covers, BOOST_FOREACH uses iterators to traverse the element sequence. Before the loop is executed, the end iterator is cached in a local variable. This is called hoisting, and it is an important optimization. It assumes, however, that the end iterator of the sequence is stable. It usually is, but if we modify the sequence by adding or removing elements while we are iterating over it, we may end up hoisting ourselves on our own petard.

http://www.boost.org/doc/libs/1%5F40%5F0/doc/html/foreach/pitfalls.html

If you don't want the end() iterator to change use resize on the vector rather than reserve.

http://www.cplusplus.com/reference/stl/vector/resize/

Note that then you wouldn't want to push_back but use the operator[] instead. But be careful of going out of bounds.

Eld
That's not his question.
GMan
right, sorry thought he was asking why the size didn't change =)changing my answer =)
Eld
Excellent answer :)
GMan
From that same link: The moral of the story is to think twice before adding and removing elements from the sequence over which you are iterating. If doing so could cause iterators to become invalid, don't do it. Use a regular for loop instead.
Murali VP
Ahhhh that explains a lot, except for why it works for a list? My guess is that .end() of a list actually returns NULL (or similar constant) because lists are implemented as doubly linked lists... so the last element of the list is defined as the element where .next() returns null which doesn't change when you add/remove elements. Am I on the right track here?
Jamie Cook
not sure what std::list uses as end(), but it doesn't matter. But the sgi site says this about lists:http://www.sgi.com/tech/stl/List.html"Lists have the important property that insertion and splicing do not invalidate iterators to list elements, and that even removal invalidates only the iterators that point to the elements that are removed." And end() is a iterator of the list so it should not be invalidated for an insert operation.
Eld
A: 

boost's foreach will terminate when it's iterator == numbers.end()

Be careful though, calling push_back can/will invalidate any current iterators you have.

Inverse
Not any, only those which follow the insertion, unless the vector grows beyond its `capacity()` (and note how he uses `reserve()`).
Pavel Minaev
+1  A: 

The question was raised in the comments as to why the Microsoft debug runtime raises an assertion during iteration over the vector but not over the list. The reason is that insert is defined differently for list and vector (note that push_back is just an insert at the end of the sequence).

Per the C++ standard (ISO/IEC 14882:2003 23.2.4.3, vector modifiers):

[on insertion], if no reallocation happens, all the iterators and references before the insertion point remain valid.

(23.2.2.3, list modifiers):

[insert] does not affect the validity of iterators and references.

So, if you use push_back (and are sure that it's not going to cause a reallocation), it's okay with either container to continue using your iterator to iterate over the rest of the sequence.

In the case of the vector, however, it's undefined behavior to use the end iterator that you obtained before the push_back.

This is a roundabout answer to the question; it's a direct answer to the discussion in the question's comments.

James McNellis