Modern C++ Features – std::begin/end and range based for loops

Here we go again with two features that have been added in C++11 that can greatly simplify the code we write. 

std::begin & Co.

`std::begin` is a free function template that by default does nothing more than calling a `begin` member function on its argument and returning the result. Its twin is `std::end` which, as you might have guessed, does the same with an `end` member function of the argument.

So, if you have one of the usual standard containers, you can write a loop over the complete container as follows:

auto numbers = std::vector<int>{1, 1, 2, 3, 5, 8};
for (auto iter = std::begin(numbers); iter != std::end(numbers); ++iter) {
  std::cout << *iter << '\n';

At first sight, this may not look like much of an improvement, but we have only just started. There are a few specializations of `std::begin` and `std::end` already built into the language, for `std::initializer_list`, `std::valarray` and plain C-style arrays, which return suitable iterator-like objects, e.g. `T*` for arrays of T.

Especially plain arrays are still often used, and they have no `begin` and `end` member functions, so in the past a full iteration loop over arrays had to look different than over any standard library container. However, since C++11 you can use the exact same code, making it possible to write template functions that can treat arrays, vectors and the like equally:

int numbers[] = {1, 1, 2, 3, 5, 8};
for (auto iter = std::begin(numbers); iter != std::end(numbers); ++iter) {
  std::cout << *iter << '\n';

Of course, there are also `std::rbegin` and `std::rend` for reverse iteration. In addition, since C++14 there are versions that explicitly take the parameter as a constant and return the corresponding `const_iterator` for standard containers and `const T*` for arrays. Those functions are called `std::cbegin`, `std::cend` etc.

Adopting third party containers

Suppose you have a third party library that defines some containers that instead of C++ style iterators provides some other means of iteration. Maybe they provide a Java style iterator class with `hasNext`, `next` and `get` functions or they only provide an indexing operator or something different.

In the past, loops over such containers had to be written different from loops over standard library containers. Even though it is usually possible to write a custom iterator class that provides `operator++`, `operator*` and anything you need for iterators, it is not possible to add `begin` and `end` member functions to such a container class that would return the corresponding custom iterators.

`std::begin` and `std::end` as free functions can be specialized for any such container to return the custom iterators, making it possible to use the standard form of full iteration loops, making the code more uniform, which reduces unnecessary attention to implementation details.

Prefer to use `begin(c)` and `end(c)` over `c.begin()` and `c.end()`, since they are more likely to continue to do the right thing if the type of `c` changes.

Which brings us to the second feature that beautifully rounds up the full iteration loops:

Range based for loops

Since C++11 there is another style of for loops, which looks like this:

for (element_decl : range_expr) statement

Which is roughly correspondent to

  auto && __range = range_expr ; 
  for (auto __it = begin(__range), __e = end(__range); __it != __e; ++__it) { 
    element_decl = *it; 

Note that this is quite a simplification of the actual wording in the C++ standard, but the spirit remains: It is a full range loop from `begin()` to `end()` of the range denoted by `range_expr`. Both `begin` and `end` are looked up via argument dependent lookup with the speciality that namespace `std` is always considered, too.

That means, that if you provide anything as `range_expr` where calling free functions `begin` and `end` makes any sense, either because you provided the functions in the associated namespace or because you overloaded `std::begin` and `std::end`, this loop construct will just work.

So, the initial examples of full range loops could be rewritten like this:

int numbers[] = {1, 1, 2, 3, 5, 8}; //or a vector...
for (auto&& num : numbers) {
  std::cout << num << '\n';

Note the `auto&&` for the loop element variable – this kind of declaration always works, independent of if you pass a const or non-const range, and regardless of what kind of value (r-value or l-value) the dereferencing of iterators will return. In fact, there has been a proposal to enable the omission of a type specifier for `num` making it `auto&&` by default.

Prefer using `auto&&` as type specifier for the loop element variable.

There are some quirks to the lookup rules:

  • If the range expression is a plain array, the will no functions be called, and range based for will just use the pointer to the array and one past the end.
  • If the range expression is of a type that has `begin` and `end` member functions, those will be called instead of any free functions.

However, those special rules only matter in corner cases, or if you do something fancy and unexpected, which is usually not a good idea. So, for completeness a few guidelines for overloading `begin` and `end` functions:

  • Don’t overload or specialize any `begin` and `end` functions for plain arrays.
  • If you write your own container classes, provide the right `begin` and `end` member functions as well as free functions and `std::begin` and `std::end` specializations (if possible).
  • Be consistent: `std::begin` and `std::end` specializations, free `begin` and `end` functions and `begin` and `end` member functions should do exactly the same to avoid confusion and surprises – implement one in terms of the others.

If you stick to these rules and to sensible and readable code in general, range based for will as good as always work (I know of no exceptions, but with C++ and its different compilers you never know for sure). So, since range based for looks different to other for loops, it very clearly says “This is a full range loop – no exceptions”.

Prefer range based for over classic iterator loops from `begin` to `end`.

Facebooktwittergoogle_plusredditlinkedinFacebooktwittergoogle_plusredditlinkedinby feather

Leave a Reply

Your email address will not be published. Required fields are marked *