Skip to main content

Section 21.3 Iterator Based Loops and Auto

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:
Listing 21.3.1.
for(list<string>::iterator it = fruit.begin(); 
    it != fruit.end(); 
    ++it
    ) {
    string currentValue = *it;
    cout << currentValue << " ";
}
The for loop is intentionally split over 3 lines to clearly show the initialization, condition, and increment parts of the loop.
  • On the first line, we initialize an iterator to the beginning of the list.
  • On the second line, we specify the loop continuation condition, which checks if the iterator has reached the end of the list.
  • On the third line, we increment the iterator to point to the next element in the list.
Inside the loop, whenever we want to access the element the iterator points to, we use the dereference operator *.
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”.

Note 21.3.1.

The auto keyword can be used to declare any variable, not just iterators. You could use it to declare an integer like auto x = 5;.
Once the type is deduced, it is fixed for that variable. So if you write auto x = 5;, then x is an integer and you cannot later assign a string to x.
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.
This means our loop can be rewritten using auto to declare the iterator to make it less wordy:
Listing 21.3.2.
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.

Activity 21.3.1.

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.

Insight 21.3.2.

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.
We can also write a range based for loop using auto to simplify the syntax even further:
Listing 21.3.3.
for(const auto& currentValue : fruit) {
    cout << currentValue << " ";
}
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.

Checkpoint 21.3.1.

Arrange the blocks to set up an iterator based for loop over the elements in a container (we don’t know/care what type) named container.

Checkpoint 21.3.2.

What is the primary benefit of using the auto keyword when declaring an iterator?
  • It makes the code more flexible.
  • It makes the iterator run faster.
  • It lets us type less.
You have attempted of activities on this page.