C++ iterator in for loop pitfalls? - c++

I see somewhere it mentions:
for ( itr = files.begin(); itr < files.end(); ++itr ) // WRONG
for ( itr = files.begin(); itr != files.end(); ++itr ) // ok
Why is the first expression wrong? I always used the first expression, and didn't have any problems.

Ordering comparisons such as <, >, <=, >= will work for random-access iterators, but many other iterators (such as bidirectional iterators on linked lists) only support equality testing (== and !=). By using != you can later replace the container without needing to change as much code, and this is especially important for template code which needs to work with many different container types.

There are different types of iterators. Only random-access iterators support the < operator. Other types of iterators (bidirectional, input, output, and forward) do not. But all iterators support the == and != operators. Therefore your code will work with all types of iterators if you use !=.

The former only works for iterators that support operator <, which not all iterators do.

Related

Confusion in concept of iterator in map in C++

Suppose I have a map named m and an iterator i for the map. Presently I am visualising a map iterator as an array index and I want to implement a code like the one given below:
for(auto i = m.begin(); i != m.end(); i++) {
auto l = i - 1; // error type 1
auto r = i + 1; // error type 1
while(l >= m.begin() && r < m.end()) { // error type 2
// ...
r++;
l--;
}
}
Now, I have got some questions which has been confusing me a bit.
For the error type 1 stated in the code, incrementing or decrementing the iterator value gives error, but the similar operation done within the loop (I mean to say i++) gives no error.
For error type 2, why does comparing two iterators (l >= m.begin()), straight up give error, but doing the similar operation in a loop does not give error?
And finally, how could I make this code work on the lines of how an array index works using this map? I hope you can understand what I am trying to implement.
Iterator for std::map is defined by standard as Bidirectional Iterator. This type of iterators can be incremented(operator ++) and decremented (operator --), but you can't perform mathematical operations on them (mostly because it would take O(n) time rather than O(1))
And again, for error 2, bidirectional iterator does not overload < operator (nor other variations), as it doesn't make sense to have compare operator with O(n) complexity. They are overloaded in random access iterators at the lowest.
To achieve what you wanted, your code can look like this:
#include <iterator> /for std::next() and std::prev()
for(auto i = m.begin; i != m.end(); ++i)
{
auto l = i;
auto r = i;
if (i != m.begin())
l = std::prev(i);
if (i != m.end())
r = std::next(i);
while (l != m.begin() && r != m.end())
{
//make sure you don't use r if it's equal to m.end()
--l;
++r;
}
Map is an associative container. The following containers are defined in the current revision of the C++ standard with associative container: set, map, multiset, multimap.
Associative Containers support bidirectional iterators. Bidirectional iterators are iterators that can be used to access the sequence of elements in a range in both directions (towards the end and towards the beginning). They are similar to forward iterators, except that they can move in the backward direction also, unlike the forward iterators, which can move only in forward direction.
Bidirectional iterators support following operations:
Is default-constructible, copy-constructible, copy-assignable and
destructible X a; X b(a); b = a;
Can be compared for equivalence using the equality/inequality
operators (meaningful when both iterator values iterate over the same
underlying sequence). a == b a != b
Can be dereferenced as an rvalue (if in a dereferenceable state).
*a a->m
For mutable iterators (non-constant iterators): Can be dereferenced
as an lvalue (if in a dereferenceable state). *a = t
Can be incremented (if in a dereferenceable state). The result is
either also dereferenceable or a past-the-end iterator. Two iterators
that compare equal, keep comparing equal after being both increased.
++a a++ *a++
Can be decremented (if a dereferenceable iterator value precedes it).
--a a-- *a--
So, + and - is not defined for it, which leads to the error1.
And so is >=, <=, >, < not defined for it, which leads to error2.
The problem is iterator doesn't work like array indices. The elements of map will be stored at different locations in memory and there is no guarantee that the between elements i and j in map, if i comes before j, then it will be stored in memory just before j. The values are not stored at consecutive locations in memory in case of map.
The ++ operator is overloaded for iterators and it will give the proper location of the next element.
Also, if you consider the points above, then comparing two iterators makes no sense because the fact that one iterator comes after another doesn't give us any important information regarding the corresponding values that will be accessed using these two iterators.
there are different iterator categories/concepts: Iterator, ForwardIterator, BidirectionalIterator, RandomAccessIterator and ContiguousIterator. They differ in available operations. Simple iterators only support step forward (operator ++), dereference (operator *) and inequality comparison (operator !=). This is the required minimum for range-based for loop. std::map::iterator is BidirectionalIterator - it doesn't support arithmetic or comparison operators.
Solving the x-y problem: I want to do "this", I wrote code that does "that"
So I presume you really do want to have access to the previous element in the map, and the next element. I presume you only want this when those 2 elements are "good"
You could recast your loop to cascade the knowledge of the 3 neighbouring iterators:
for(auto r = m.begin(), e= m.end(), i= e, l= e; r != e; l =i, i =r, r++)
{
if (l != e)
{
// All 3 iterators are good here
}
}

Why no operator+ for std::list iterators?

I was about to write code like this:
std::list<whatevertype> mylist;
// ...
std::list<whatevertype>::iterator it;
for(it = mylist.begin(); it != mylist.end(); ++it) {
// ...
if(some condition)
mylist.erase(it);
}
But I realized, this code is wrong: mylist.erase(x) will invalidate the iterator it, so the ++it is likely to fail.
So I tried changing it to
std::list<whatevertype>::iterator it;
std::list<whatevertype>::iterator nextit;
for(it = mylist.begin(); it != mylist.end(); it = nextit) {
// ...
nextit = it + 1;
if(some condition)
mylist.erase(it);
}
But, to my surprise, this failed: evidently operator+ is not defined for std::list iterators.
I've since found this other question and learned that the standard idiom for deleting "out from under" an iterator is more like
for(it = mylist.begin(); it != mylist.end(); ) {
if(some condition)
it = mylist.erase(it);
else ++it;
}
I believe I could also get away with
for(it = mylist.begin(); it != mylist.end(); ) {
// ...
std::list<whatevertype>::iterator previt = it;
++it;
if(some condition)
mylist.erase(previt);
}
But my question is, is there a reason that operator+ is not defined for these iterators?
One rule they had with the std iterators and collection was to make expensive things verbose.
On a list iterator, it+50 takes O(50) time. On a vector iterator, it+50 takes O(1) time. So they implemented + on vector iterators (and other random access iterators) but not on list iterators (and other weaker iterators).
std::next and std::advance and std::prev can solve your problem easier:
auto previt = std::prev(it);
or
auto nextit = std::next(it);
these also take a count, but because they are an explicit function call it was decided that them being expensive is acceptable.
Among other things, you can search for calls to std::next and std::prev and get iterator manipulation; + is heavily overloaded and finding the expensive calls is hard.
Note that std::basic_string doesn't follow the same conventions as other std containers.
It isn't that + is missing for all iterators. It is missing for std::list iterators.
That's because a list iterator is incredibly inefficient at random access. Therefore, making random access easy is a bad idea.
You can use std::advance. It makes it more evident that you are moving across the list one element at a time.
std::list uses a BidirectionalIterator which only defines increment and decrement. As std::list is a linked list the implementation of the iterator can only move one node at a time.
The interface is designed to make sure you know that moving by more than one element isn't a simple operation like it is with other iterators like a RandomAccessIterator returned from a std::vector.
see http://en.cppreference.com/w/cpp/concept/Iterator for a definition of the different iterator types.

Determine order of set iterators

I have two iterators (say it1 and it2) into the same std::set<int>. They are obtained through lower_bound and upper_bound, therefore they are not safe to dereference (they could equal end()).
Is there an easy and safe way of telling which one goes first?
I could call std::distance(it1, it2) and std::distance(it2, it1), but that does not seem to help since if it1 != it2 then one of the calls is UB. I could test *it1 < *it2, but only if no iterator points to the end(). Finally, I could first test for the end iterator and then do the above comparison on values.
Is there an elegant solution based purely on iterators and not involving the values, i.e., dereferencing? I am willing to use up to c++14 and maybe boost.
EDIT (in reponse to comments):
I use a set because I want fast lookup and insertion, in particular much faster than linear complexity. A sorted vector would be a possible alternative, it would trivially solve the problem, but insertion and removal are linear time operations.
In my opinion, the best way is to fix your code logic and have [it1,it2) always be a valid range; if this turns out not possible (but how can it be ?), you may use something like
// O(N), forward iterators, it1, it2 should belong to range
template<class Iter>
bool precedes_or_is_equal( Iter it1, Iter it2, Iter end )
{
while( it1 != end && it1 != it2 ) ++it1;
return it1 == it2;
}

C++: list iterators vs. vector iterators

I thought the idea of the iterator object was that you can apply it similarly to the C++ container classes. When I try to iterate through a list object, however, I tried using
for(list<int>::iterator it = obj.begin(); it < obj.end(); it++){
// some code
}
And I got an error. Why doesn't this work? Why would it work for vector::iterator? Is it just because of the implementation of list being bi-directional linked lists? I thought the iterator object abstracts that notion of moving through containers, thereby allowing it to operationally be the same, whether for vectors or lists.
I'd really appreciate a clarification.
This does not work because, unlike std::vector iterators, std::list iterators are not random-access - they are sequential. You need to use != on them:
for(list<int>::iterator it = obj.begin(); it != obj.end(); it++)
In general, it's a good idea to use "not equals" on all iterators when you are looking to cover the entire range, even when these iterators allow comparisons for < and >. There is also an argument in favor of using != in your regular for loops, too, because it gives you the strongest postcondition.
You have to compare with != as list iterators are scattered throughout all memory in random order.
Use: for(list<int>::iterator it = obj.begin(); it != obj.end(); it++)
That's because list does not support random access iterators, but only forward iterators. Therefore, operator < is not defined for iterators of a list. You have to use operator != for inequality comparisons.
Operator arithmetic, including ordering-comparison operators (like <), is only defined for random access iterators. If you change the code to use !=, it will work (assuming obj is a list<int>):
for(list<int>::iterator it = obj.begin(); it != obj.end(); it++){
// some code
}

C++ equivalent of Python difference_update?

s1 and s2 are sets (Python set or C++ std::set)
To add the elements of s2 to s1 (set union), you can do
Python: s1.update(s2)
C++: s1.insert(s2.begin(), s2.end());
To remove the elements of s2 from s1 (set difference), you can do
Python: s1.difference_update(s2)
What is the C++ equivalent of this? The code
s1.erase(s2.begin(), s2.end());
does not work, for s1.erase() requires iterators from s1.The code
std::set<T> s3;
std::set_difference(s1.begin(), s1.end(), s2.begin(), s2.end(), std::inserter(s3, s3.end());
s1.swap(s3);
works, but seems overly complex, at least compared with Python.
Is there a simpler way?
Using std::set_difference is the idiomatic way to do this in C++. You have stumbled across one of the primary differences (pun intended) between C++/STL and many other languages. STL does not bundle operations directly with the data structures. This is why std::set does not implement a difference routine.
Basically, algorithms such as std::set_difference write the result of the operation to another object. It is interesting to note that the algorithm does not require that either or both of the operands are actually std::set. The definition of the algorithm is:
Effects: Copies the elements of the range [first1, last1) which are not present in the range [first2, last2) to the range beginning at result. The elements in the constructed range are sorted.
Requires: The resulting range shall not overlap with either of the original ranges. Input ranges are required to be order by the same operator<.
Returns: The end of the constructed range.
Complexity: At most 2 * ((last1 - first1) + (last2 - first2)) - 1 comparisons
The interesting difference is that the C++ version is applicable to any two sorted ranges. In most languages, you are forced to coerce or translate the calling object (left-hand operand) into a set before you have access to the set difference algorithm.
This is not really pertinent to your question, but this is the reason that the various set algorithms are modeled as free-standing algorithms instead of member methods.
You should iterate through the second set:
for( set< T >::iterator iter = s2.begin(); iter != s2.end(); ++iter )
{
s1.erase( *iter );
}
This will could be cheaper than using std::set_difference - set_difference copies the unique objects into a new container, but it takes linear time, while .erase will not copy anything, but is O(n * log( n ) ).
In other words, depends on the container, you could choose the way, that will be faster for your case.
Thanks David Rodríguez - dribeas for the remark! (:
EDIT: Doh! I thought about BOOST_FOREACH at the very beginning, but I was wrong that it could not be used.. - you don't need the iterator, but just the value.. As user763305 said by himself/herself.
In c++ there is no difference method in the set. The set_difference looks much more awkward as it is more generic than applying a difference on two sets. Of course you can implement your own version of in place difference on sets:
template <typename T, typename Compare, typename Allocator>
void my_set_difference( std::set<T,Compare,Allocator>& lhs, std::set<T,Compare,Allocator> const & rhs )
{
typedef std::set<T,Comapre,Allocator> set_t;
typedef typename set_t::iterator iterator;
typedef typename set_t::const_iterator const_iterator;
const_iterator rit = rhs.begin(), rend = rhs.end();
iterator it = lhs.begin(), end = lhs.end();
while ( it != end && rit != rend )
{
if ( lhs.key_comp( *it, *rit ) ) {
++it;
} else if ( lhs.key_comp( *rit, *it ) ) {
++rit;
} else {
++rit;
lhs.erase( it++ );
}
}
}
The performance of this algorithm will be linear in the size of the arguments, and require no extra copies as it modifies the first argument in place.
You can also do it with remove_if writing your own functor for testing existence in a set, e.g.
std::remove_if(s1.begin(), s1.end(), ExistIn(s2));
I suppose that set_difference is more efficient though as it probably scans both sets only once
Python set is unordered, and is more of an equivalent of C++ std::unordered_set than std::set, which is ordered.
David Rodríguez's algorithm relies on the fact that std::set is ordered, so the lhs and rhs sets can be traversed in the way as exhibit in the algorithm.
For a more general solution that works for both ordered and unordered sets, Kiril Kirov's algorithm should be the safe one to adopt if you are enforcing/preserving the "unorderedness" nature of Python set.