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&amp;&amp; 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&amp;&amp; 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, no functions will 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`.

Previous Post
Next Post


  1. Does it make sense to use “erase” in a loop for std::vector?

    std::vector v{ 1, 2, 3, 4, 5, 6 };
    std::vector::iterator it = v.begin();
    while (it != v.end()) {
    if (condition)
    it = v.erase(it); // after erasing, ‘it’ will be set to the next element in v
    ++it; // manually set ‘it’ to the next element in v

    See more:–vector


    1. Often it is not considered good practice to use raw loops like this for containers, especially in this case, where the container is modified while iterating over it. The idiomatic way to achieve the same would be

      const auto newEnd = v.remove_if(begin(v), end(v), condition);
      v.erase(newEnd, v.end());


  2. A bit late in the game, but this paragraph needs some help:
    “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.”

    Other than that, these articles are awesome.


    1. Hi Patty,
      thanks for the hint – it’s never too late to fix things like that 😉

      I’m glad you like the articles!


  3. How to find out what type is returned by begin() and end() functionts, because I have an exercise where is required to write without auto, decltype type and alias.


    1. There are several possibilities. First, you can just say int instead of auto and the compiler will give you an error message telling you that some type is not convertible to int. However, this will usually give you a messy templated type while there are typedefs/type aliases that are canonical and more pronounceable.
      For begin() and end() on containers there is a convention that is used throughout the standard library with little or no exceptions: some_container_type::begin() returns a some_container_type::iterator (or const_iterator if you call it on constant objects). The same goes for end.
      In the end, you can always use a C++ reference to look up what the functions of a standard container return:


Leave a Reply

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