ereOn ereOn - 1 year ago 53
C++ Question

How to make my custom type to work with "range-based for loops"?

Like many people these days I've been trying the different features that C+11 brings. One of my favorites is the "range-based for loops".

I understand that:

for(Type& v : a) { ... }

Is equivalent to:

for(auto iv = begin(a); iv != end(a); ++iv)
Type& v = *iv;

And that
simply returns
for standard containers.

But what if I want to make my custom type "range-based for loop"-aware ?

Should I just specialize

If my custom type belongs to the namespace
, should I define

In short, what are the guidelines to do that ?

Answer Source

The standard has been changed since the question (and most answers) were posted in the resolution of this defect report.

The way to make a for(:) loop work on your type X is now one of two ways:

  • Create member X::begin() and X::end() that return something that acts like an iterator

  • Create a free function begin(X&) and end(X&) that return something that acts like an iterator, in the same namespace as your type X

And similar for const variations. This will work both on compilers that implement the defect report changes, and compilers that do not.

The objects returned do not have to actually be iterators. The for(:) loop, unlike most parts of the C++ standard, is specified to expand to something equivalent to:

for( range_declaration : range_expression )


  auto && __range = range_expression ;
  for (auto __begin = begin_expr,
            __end = end_expr;
            __begin != __end; ++__begin) {
    range_declaration = *__begin;

where the variables beginning with __ are for exposition only, and begin_expr and end_expr is the magic that calls begin/end

The requirements on the begin/end return value are simple: You must overload pre-++, ensure the initialization expressions are valid, binary != that can be used in a boolean context, unary * that returns something you can assign-initialize range_declaration with, and expose a public destructor.

Doing so in a way that isn't compatible with an iterator is probably a bad idea, as future iterations of C++ might be relatively cavalier about breaking your code if you do.

As an aside, it is reasonably likely that a future revision of the standard will permit end_expr to return a different type than begin_expr. This is useful in that it permits "lazy-end" evaluation (like detecting null-termination) that is easy to optimize to be as efficient as a hand-written C loop, and other similar advantages.

¹ Note that for(:) loops store any temporary in an auto&& variable, and pass it to you as an lvalue. You cannot detect if you are iterating over a temporary (or other rvalue); such an overload will not be called by a for(:) loop. See [stmt.ranged] 1.2-1.3 from n4527.

² Either call the begin/end method, or ADL-only lookup of free function begin/end, or magic for C-style array support. Note that std::begin is not called unless range_expression returns an object of type in namespace std or dependent on same.

In the range-for expression has been updated

  auto && __range = range_expression ;
  auto __begin = begin_expr;
  auto __end = end_expr
  for (;__begin != __end; ++__begin) {
    range_declaration = *__begin;

with the types of __begin and __end have been decoupled.

This permits the end iterator to not be the same type as begin. Your end iterator type can be a "sentinal" wich only supports != with the begin iterator type.

A practical example of why this is useful is that your end iterator can read "check your char* to see if it points to '0'" when == with a char*. This allows a C++ range-for expression to generate optimal code when iterating over a nul-terminated char* buffer.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download