Should every programmer make his/her own vector, list, and so on? Oh, of course not. So some time back, the Standard Template Library (STL) was put into the standard. In STL you’ll find containers like list and vector; strings, as we already use them; and commonly needed functions like swap, find, and copy.
You’ll also find an annoying emphasis on efficiency. I say “annoying” because STL promotes efficiency by disabling inefficient things. If you want to use operator[] with a list, you can forget it; it’s too slow (O(N) time, if you do O notation). If you want [], the makers of STL reasoned you can use a vector. They’re right. But I still get to be annoyed.
We’ve got to get at the elements of a list somehow. How? Let’s deal with that now.
Iterators
operator[] for list doesn’t exist. Entrys are private. What can we do?

A list, with its begin() and end() access functions, and an iterator i denoting the second element. end() is one step past the last element
end() refers not to the last element, but to one past the last element. Think of the list as a train of cars, and this is its caboose. We check it to see if we’re going too far, using != not <. (Whether one iterator is less than another is not defined – but whether they’re equal is.)
++i and i++ work as expected; they take you to the next element.
To get not the iterator but the thing it’s referring to, put the * in front, as in the loop shown earlier.
That’s all there is to it!
Is your reaction something like “But what is an iterator?”?
Formally, it’s just as described: it’s a thing that refers to an element in the list, and when you say ++, it goes to the next element.
An iterator class for List
And now we can get to the List’s data outside the List class.
…with vector too
- Easy rewriting of code. Consider these two versions of code with a vector:for (int i = 0; i < v.size(); ++i)doSomething(v[i]);andfor (vector<T>::iterator i = v.begin(); i != v.end(); ++i)doSomething(*i);
I write this and later think, No, I see now vector isn’t the way to go; list is better.
Some member functions of vector already require iterators. insert, for instance.
Generic programming. Suppose there’s something you want to do with different types of container:
Since find for list must use iterators, find for vector uses iterators too. That way you can learn one way to call the function, and it’ll work regardless of your choice of container. The section “<algorithm> (optional)” at the end of the chapter introduces a few of many such functions STL provides.
But if you do use int index for vectors as before, the sky won’t fall.
const and reverse iterators
The compiler gives an error message that boils down to: myList is const, but you’re using an iterator with it, and that’s a type conflict.
That’s all it takes.
If you like you can go through the container backward (note the rbegin and rend – the same as begin and end, only in reverse):
If you don’t plan to change the container, use const_iterator.
If you do, use iterator.
If you’re going backward, stick reverse_ in there somewhere.
Antibugging
- You get an error message too long to read:conversion from 'std::__cxx11::list<std::pair<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >::const_iterator' {aka 'std::_List_const_iterator<std::pair<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >'} to non-scalar type 'std::__cxx11::list<std::pair<std::__cxx11::basic_string<char>, int> >::const_iterator' {aka 'std::_List_const_iterator<std::pair<std::__cxx11::basic_string<char>, int> >'} requestedRip out things that don’t look important. This gets usconversion from list<pair<string, string > >::const_iterator to list<pair<string, int> >::const_iterator requested.
My mistake is now clearer: I forgot what I wanted a pair of.
You get pages of error messages, reporting errors in system libraries. Look for your own filename(s) in the messages and focus on those.
- 1.
Using iterators in a for loop, write a function reverse which returns a reversed copy of a List you give it.
- 2.
Now write it so that instead of passing a List you pass in two iterators, maybe its begin() and its end().
- 3.
Add a const_iterator class to List.
You’ll need new const versions of begin and end, to return const_iterator instead of iterator.
- 4.
Exercise 8 in Chapter 22’s last set of exercises dealt with equipping a List to be traversed backward. Using that, incorporate reverse_iterator and const_reverse_iterator into the List class.
- 5.
Using iterators in for loops, write functions to convert back and forth between STL lists and vectors.
Getting really lazy: range-based for and auto
It’s a “range-based” for loop: it uses iterators – expects begin() and end() and so works for vector, list, or whatever – but we don’t have to write all that out (yay). (Code in this section is collected and compilable in source code project/folder ch23/range-based-for-and-auto.)
I use auto when type names are so long I think my fingers will fall off (list<vector<int>>::const_reverse_iterator – aigh!) and in these range-based loops. I think it is overkill for simple, basic-type declarations.1
Spans
The language Pascal (started in 1970) lets you pass in arrays as function parameters, with the size as part of the type. Problem: You need a different function for every array size.
C and C++, as you know, don’t keep that size around, so you have the extra work of passing it in as another parameter.
A program that uses spans to read, print, and dissect a phone number
The const in front of the spans in print and read means the spans’ structure won’t be altered by the function. (So you can send in an r-value, a thing that can’t be assigned to, like span (remainder, 7).) But its elements can be changed.
The const int in print’s span means the ints in it are const too. Use this if you don’t want the function to change the array.
- 1.
Rewrite any program you had in Chapter 10 in which arrays were passed into functions, to use ranges, auto, and spans.
- 2.
Suppose you’ve got data, new cases per day, from when the coronavirus first became big news (Figure 23-2). To smooth the data and help us see if it’s generally increasing or decreasing – if we’re flattening that curve – calculate for each day the average for the three before it, the three after it, and itself. (You can skip the first 3 and last 3 days of your data set.) Then print those averages. Use ranges, auto, spans, and subspan .

Sample data for coronavirus cases, using a 7-day average for smoothing
- 3.
(Uses file I/O) Do the preceding exercise, getting data from a file. You don’t know how much data is in the file, so use dynamic arrays. At least one function (the one that prints) should be passed a dynamic array.
initializer_lists (optional)
One thing I was sorry to give up, going from arrays to more sophisticated containers, was the bracketed initializer list, as in int array [] = {0, 1, 2, 3};. Initializing element by element is more trouble.
Adapting Vector to use an initializer_list
- 1.
Write and test a constructor that takes an initializer_list, in class Point2D.
- 2.
…in class Fraction.
- 3.
If you go in order through an initializer_list, using List::push_front to add its elements to a List, the order will be reversed. (Work it out on paper if necessary, to confirm.) So go back and do Exercise 8 in the last chapter if you haven’t – it provides List::push_back – and give List a constructor that takes an initializer_list.
<algorithm> (optional)
Many things you may want to do with containers are in the include file <algorithm> .
find returns an iterator referencing the first element of digits equal to 7. If there is no such element, it returns digits.end().
Most of these functions should work for any container type. ranges::sort (digits); does what you think it does. (Operator < for its elements must be defined.) But if you want to sort a list, you’ll have to use its member function: myList.sort ();. Go figure.
int is what we have a list of, cout is where it’s going, and " " is what to print after each element. You’ll want #include <iterator> (though it may be provided by another system include).
There are more useful functions; an Internet search will get you what you need. cplusplus.com and cppreference.com are good places to start.
Antibugging
- You add or remove elements in or from a loop, and get a crash. When you do anything to change the contents of your container, it may invalidate iterators already in the container. Consider this code (assume digits is a vector):for (auto i = digits.begin(); i != digits.end(); ++i)if (isEven(*i))digits.erase (i); // erase element referenced by i// (different "erase" -- a member of // vector)
After you erase whatever’s at i, i points to a nonexistent element. The loop increments i and gets to another nonexistent element at who-knows-where, and the result is unpredictable.
Which iterators are invalidated by this kind of erase and how to fix it depends on container type. You can investigate and write a fix for whatever container you chose, or use the built-in erase (myContainer, predicate) function. I know what I’d recommend.
- 1.
In the string “SoxEr776asdCsdfR1234qqE..T12Ci-98jOapqwe0DweE” there is a secret code. Use an <algorithm> function to extract only the capital letters and read the code.
- 2.
(Uses file I/O) First, make a file of strings. Then make two copies of it. In one, alter the order and the capitalization of some of the strings. In the other, replace some of the strings.
Now write a program that can read two files into vectors or lists and, using STL functions, tell if the files are different, ignoring order and capitalization. Use an STL function not covered in this section (so you’ll need an Internet search) that changes a string to all caps or all lowercase. To find what’s in one sequence but not the other, consider this algorithm:for each element in sequence 1 (using iterators)use the "find" function to see if it's also in sequence 2 - 3.
Do the preceding problem, but instead of using find and a loop, use set_difference (also not covered earlier).