At the end of the previous section, we saw how to use an iterator to traverse a list step by step. If we want to process all the elements in a container, we can use a loop that uses an iterator to move through the collection one element at a time:
We created an iterator variable by explicitly specifying its type, which can be a bit cumbersome. Here is the code we used to create an iterator for a set of strings:
The syntax for the type of the iterator is a bit cumbersome: list<string>::iterator. There is no such thing as βjust an iteratorβ. Every iterator is tied to a specific container type. And so, we must specify the type of the container the iterator is associated with. This can get especially long and unwieldy when working with more complex containers like a list of multi-dimensional vectors.
// A list of 2D grids (each grid is a vector of vector of ints)
list<vector<vector<int>>> gridList;
// An explicit iterator type for the gridList
list<vector<vector<int>>>::iterator it = gridList.begin();
We could use a typedef to make this less wordy. Something like this would help:
// Define a type alias for the iterator type
typedef list<vector<vector<int>>>::iterator GridListIterator;
// A list of 2D grids (each grid is a vector of vector of ints)
list<vector<vector<int>>> gridList;
// Now use the type alias to declare the iterator
GridListIterator it = gridList.begin();
But C++ provides another solution: the auto keyword. When we use auto to declare a variable, the compiler automatically deduces the type of the variable based on the expression used to initialize it.
// A list of 2D grids (each grid is a vector of vector of ints)
list<vector<vector<int>>> gridList;
// Compiler will examine the right side to determine the type of 'it'
auto it = gridList.begin();
The type of it will be set to the return type of gridList.begin(). In this case, that will be a list<vector<vector<int>>::iterator. This allows us to avoid writing out the full type name, making the code cleaner and easier to read. It is hopefully clear to any C++ programmer that it is βan iterator over the gridListβ.
We have intentionally not used auto yet, because it is important to clearly understand data types before relying on type deduction. And using auto for everything can make code harder to read even for experienced programmers. auto should only be used when it improves code clarity or improves flexibility.
Not only does this make the code shorter, it also makes it more flexible. If we later decide to change the type of fruit from a list<string> to a vector<string>, we do not have to change the loop code at all. The type of it will automatically adjust to be the correct iterator type for whatever container type fruit is.
Both a list and a vector support begin() and end() methods that return iterators that represent the start and end of the collection. And the different iterators both know how to move forward when told to do ++it and access the current item when told to do *it. So the same loop logic works for both containers.
Try changing the type of the container in ListingΒ 21.3.2 to a vector. Change line 9 to declare a vector<string> instead of the set<string>. Then run the code again.
Iterators provide a powerful abstraction that allows us to work with different types of containers in a uniform way. They enable algorithms to operate on a wide variety of data structures without needing to know the details of how those structures store their elements.
A range-based for loop is essentially syntactic sugar for an iterator-based loop. The compiler will translate it into something like the iterator loop we wrote earlier.