14.4. Iterable ADT’s

How can we visit each element in a container and remain ignorant of the underlying container implementation details? For example, given:

array<string, 3> names = {"Alice", "Bob", "Clara"};
std::list<int>   ages  = {27, 3, 1};

What options do we have for operating on each element in names and ages? A traditional for or while loop works for names:

for (unsigned i=0; i < names.size(); ++i) {
  cout << names[i] << '\n';
}

unsigned i = 0;
while(i < names.size()) {
  cout << names[i++] << '\n';
}

However, this code does not compile:

for (unsigned i=0; i < ages.size(); ++i) {
  cout << ages[i] << '\n';
}

unsigned i = 0;
while(i < ages.size()) {
  cout << ages[i++] << '\n';
}

Traditional loops using an int index do not work with containers that do not overload operator[]. Containers in this category include list, set, and map.

We solve this problem by avoiding explicit indexing altogether. The range-based for loop provides a more readable equivalent to the traditional for loop:

for (string s: names) {
  cout << s << '\n';
}

// better: avoids copying
for (const auto& s: names) {
  cout << s << '\n';
}

The same syntax can be used for any STL container:

std::list<int>  ages  = {27, 3, 1};

for (const auto& a: ages) {
  cout << a << '\n';
}

When you need to loop over each element in a collection, the range-for loop has completely abstracted away the idea of moving from one element to the next.

We say these containers are iterable.


More to Explore