computer science goes bonk / all posts / rss / about

foreach a jolly good fellow

C++11 has a new way of doing for loops. Well, "new" might be a bit subjective, seeing as how Perl has had foreach since like 1994. But whatever. The standards committee took their time, attended a lot of meetings in exotic locations [Indiana], and made sure that the new range-based for loops wouldn't turn into another exception specification. The result is a dramatic improvement over what C++03 offered:

std::set<char> letters = {'o', 'm', 'g'};
for (char c: letters) {  // range-based for loop
  std::cout << c << '\n';
}

But how does it work?

Under the covers, when the compiler sees a statement of the form:

for (range-declaration: expression) statement

...it translates it into something like:

{
  auto && __range = range-init;
  for (auto __begin = begin-expr, __end = end-expr;
       __begin != __end;
       ++__begin) {
    range-declaration = *__begin;
    statement
  }
}

range-init is usually ( expression ) unless we're iterating over an initializer list, in which case it's just the raw list.

If the type of expression is an array, then begin-expr is __range and end-expr is __range + __bound, where __bound is the size of the array.

char letters[] = {'o', 'm', 'g'};
for (char c: letters) {  // works for arrays, too
  std::cout << c << '\n';
}

Otherwise, begin-expr is std::begin(__range) and end-expr is std::end(__range).

Iterating over custom types

std::begin and std::end look for begin/end methods that expose iterators, so you can iterate over custom types as long as they're well-behaved:

#include <iostream>
#include <algorithm>
#include <memory>

template <typename T>
class Foo {
 public:
  Foo(const std::initializer_list<T> &init) :
    m_size(init.size()),
    m_data(new T[init.size()]) {
    std::copy(init.begin(), init.end(), m_data.get());
  }
  // expose begin/end for iteration support
  T* begin() { return m_data.get(); }
  T* end() { return m_data.get() + m_size; }
 private:
  size_t m_size;
  std::unique_ptr<T[]> m_data;
};

int main() {
  Foo<int> foo = {3, 1, 4, 1, 5, 9};
  for (auto c: foo) {
    std::cout << c << '\n';
  }
}

Iterating over initializer lists

std::initializer_list exposes begin and end methods, so it can be iterated over:

for (auto c: {3, 1, 4, 1, 5, 9}) {
  std::cout << c << '\n';
}