Professional CMake:

A Practical Guide

Learn to use CMake effectively with practical advice from a CMake co-maintainer. You can also have the author work directly with your team!

Container iteration with C++11

C++11 introduced some features which make working with STL containers much easier. One common situation is the need to iterate over a container and to perform some operation(s) on each item. Consider the following typical example:

std::vector<SomeType> container;
// ...

for(std::vector<SomeType>::iterator iter = container.begin();
    iter != container.end();
    ++iter)
{
    const SomeType& item = *iter;
    // ...
}

This syntax has a couple of drawbacks:

  • It is rather verbose
  • Every aspect of the container’s type needs to be included in the definition of iter

One of the typical ways developers often try to minimise the above two disadvantages is to use a typedef. For example:

typedef std::vector<SomeType> MyContainer;
MyContainer container;
// ...

for(MyContainer::iterator iter = container.begin();
    iter != container.end();
    ++iter)
{
    const SomeType& item = *iter;
    // ...
}

This mostly addresses the second point about repeating the container’s type details, but it doesn’t really help a great deal with readability. Another alternative is to use the STL’s std::foreach, which mostly addresses the first point if you already have a function which implements the operation you want to perform on each item. For example:

std::foreach(container.begin(), container.end(), SomeFunc);

But in practice, you often don’t have or don’t want to write a separate function just for the for loop body. C++11 provides a more convenient way to improve the readability aspect by using a new syntax for range-based for loops rather than iterators. To iterate over an entire container, the new syntax would be:

for(const SomeType& item : container) { ... }

That’s a big improvement. We no longer need to mention anything about iterators and instead we only need to mention the two things we are really interested in, the container and each item in it. Notice also how we no longer need to specify what type of container we are iterating over. This means we also gain the advantage of being able to change the container type and our for loop remains valid without further modification.

But we can do even better. The above still includes the type of the items held in the container (i.e. SomeType), but ideally we’d like to avoid that too. Conveniently, C++11 also added the auto keyword which can be used here to let the compiler auto-detect the underlying item type:

for(const auto& item : container) { ... }

Now we have avoided mentioning anything at all about the container’s type. Hence, if at some point in the future we want to change the container type or the type of the objects it holds, the for loop definition won’t need to be modified (assuming whatever we do inside the for loop still remains valid, of course).

In the case where the container is always going to hold a type that is cheap to copy (e.g. a built-in type like int or double), these loops are often just written as:

for(auto item : container) { ... }

It doesn’t get much simpler than that! Compared to what we started with, this is considerably easier to read, less prone to errors and is especially useful when writing templated code because it automatically adapts to whatever type of container we use it with.

This is all well and good, but what if you have some container which isn’t based on the STL? What if you have your own container type, can you still use the techniques described above? The short answer is yes, as long as your container acts like a range. It needs to satisfy one of the following criteria:

  • An array whose size and type is known to the compiler.
  • Provides begin() and end() member functions.
  • The expressions begin(container) and end(container) can be found with argument-dependent lookup (ADL).

Consider two examples, MyIntContainer which provides begin() and end() member functions and SomeoneElsesContainer which doesn’t (the latter might be a class defined by some third party code which you cannot modify):

class MyIntContainer
{
public:
    int* begin();
    int* end();
    //...
};

class SomeoneElsesIntContainer
{
    // Does not provide begin() or end()
    // ...
};

int* begin(SomeoneElsesIntContainer& container);
int* end(SomeoneElsesIntContainer& container);

Given the above, the following are all examples of valid range-based for loops:

MyIntContainer mine;
SomeoneElsesContainer theirs;

for(auto item : mine) { ... }
for(auto item : theirs) { ... }
for(auto item : {23, 10, 5, 17} ) { ... }

The last of these three for loops also highlights another useful C++11 feature – initialiser lists. These have known type and size, so they also satisfy the criteria of a range expression. I’ll save discussion of these for a separate future article.

2 thoughts on “Container iteration with C++11”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.