C++: list iterators vs. vector iterators - c++

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
}

Related

Iterating in reverse direction using rbegin() and begin()

When we are iterating in reverse direction, I see that most people use the following structure:
for (auto it = vec.rbegin(); it != vec.rend(); it++)
{
// block of code //
}
But for a long time, I have a doubt about using this, and I want to know why the following code does not work.
As we know, the last element will have the highest index than any element index in the array, and the array is going to take contiguous memory.
My primary doubt is when iterating backwards, why shouldn't we use it--?
I want the reason why the following code is not going to work. I am running the loop from rbegin, that is the last element, and I am going until the first element. I am decrementing it by one in every iteration.
for (auto it = vec.rbegin(); it >= vec.begin(); it--)
{
cout << *it << endl;
}
Even the below code is not working, why?
for(auto it = vec.rbegin(); it >= vec.begin(); it++)
{
cout << *it << endl;
}
First of all, in the given codes, the for loop's conditions are making issue due to type-mismatch.
The vec.rbegin() gives the std::vector::reverse_iterator, and the vec.begin() gives the std::vector::iterator; those are different types and can not be compared. Hence, you get compiler errors in those places.
When iterating backwards, why shouldn't we use it--?
See the following reference picture from std::reverse_iterator
When you use rbegin(), you start from the last element. In order to advance further (like every iterator implementation) it uses the operator++. Advance here means, iterating backwards direction, because the starting point is the last element. Therefore, you should be using it++ or ++it instead.
For the last for loop example, however, there is only a type-mismatch issue. Using ✱std::reverse_iterator::base(), you could get/ convert the reverse iterator to the corresponding base iterator, and it can be compared with the vec.begin().
That means the following change will make it work:
for (auto it = vec.rbegin(); it.base() != vec.begin(); ++it)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
std::cout << *it << " ";
}
See a demo
Side Note:
Even though, the above is possible, I would strongly suggest use the same iterators for comparison, which provides the code more natural look, and easy to understand for the fellow devs and less error-prone.
✱Read more: Can I convert a reverse iterator to a forward iterator?
In all, it is just a design issue, the designer designed the begin, rbegin, end, rend in that way.
Take an example of a container with three elements {1,2,3}.
begin() points to 1, end() points to the position after 3
rbegin() points to 3, rend() points to the position before 1.
You can understand rbegin() as a special data struct of a special pointer (aka iterator) such that + operator would be overloaded into -.
You can but not recommended to mix rbegin() with begin() cause they are different things. And mixing is always error-prone for most of the time.
Reverse iterators are designed to mimic forward iterators (and iterators in general are designed to mimic pointers), so algorithms can be written in an agnostic way that works with both types. All iterators advance with operator++ and decrement with operator-- , where
forward iterators advance in a forward direction and decrement in a backwards direction
reverse iterators advance in a backward direction and decrement in a forward direction

traverse the std::map with less than comparison between iterators

When I'd like to traverse a map in C++, we may use the following technique:
for (auto i = m.begin(); i != m.end(); i++)
{ ... ... }
Why cannot we use the following instead:
for (auto i = m.begin(); i < m.end(); i++)
{ ... ... }
My guess is because the elements in an associative container are not stored in sequential order like sequential containers, is that right?
The comparison operator < requires random access iterators.
map only provides Bidirectional iterators. The reason is that you cannot say with just such an iterator if another iterator is before or after in constant time (yes, they are not one after the other in memory).
As != is valid for all types of iterators, use it instead of the < version. It's portable if you change the container type.

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.

What's the point of iterators? [duplicate]

This question already has answers here:
Why use iterators instead of array indices?
(27 answers)
Closed 10 years ago.
Why should I use iterators?
For example if I have code like this:
for (int i = 0; i < vec.size(); i++)
cout << vec[i];
what would be the advantage of writing
for (vector<int>::iterator it != vec.begin(); it != n.end(); ++it)
cout << *it;
Also, why is writing i < vec.size() and i++ more common in the first example and it != begin() and ++it more common in the second example? What is the difference how you increment it and why not always use an equal sign?
I understand iterators can be useful in C++11 range-based for loops and some STD algorithms, but why should I do it in normal code, since it is more verbose?
Well not all containers have random access so you can do lookup on an index, so to normalize the interface iterators are fairly useful. Consider std::list. It doesn't support random access via a [] operator.
Taking this into account, to work across many heterogeneous types of containers, many STL functions like std::copy take iterators.
The point is that iterators allow you to iterate over anything that supports iterators in a generic fashion.
As for it being more verbose, the extra verbosity isn't horrible (and your example could be slightly improved using auto or using the C++11 range-based for loop) but that's really a stylistic issue.
Lets say we have this code:
typedef std::vector<std::string> strings;
strings strs;
for( strings::const_iterator it = strs.begin(); it != strs.end(); ++it ) {
}
And later for watever reason we decide to switch to std::list. So we just replace typedef and code:
typedef std::list<std::string> strings;
strings strs;
for( strings::const_iterator it = strs.begin(); it != strs.end(); ++it ) {
}
Will work as before. But code with index variable will fail. Imagine what if you need to write a template code.
The tl;dr is that iterators work better in a general case for different kinds of objects (when for example the size() method may be slow).
If you want to read more about it:
Why use iterators instead of array indices?
Iterators.. why use them?

C++ iterator in for loop pitfalls?

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.