Like many people these days I have 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 begin()
simply returns a.begin()
for standard containers.
But what if I want to make my custom type "range-based for loop"-aware?
Should I just specialize begin()
and end()
?
If my custom type belongs to the namespace xml
, should I define xml::begin()
or std::begin()
?
In short, what are the guidelines to do that?
Best Answer
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 typeX
is now one of two ways:Create member
X::begin()
andX::end()
that return something that acts like an iteratorCreate a free function
begin(X&)
andend(X&)
that return something that acts like an iterator, in the same namespace as your typeX
.¹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:becomes:
where the variables beginning with
__
are for exposition only, andbegin_expr
andend_expr
is the magic that callsbegin
/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-initializerange_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 thanbegin_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 anauto&&
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 afor(:)
loop. See [stmt.ranged] 1.2-1.3 from n4527.² Either call the
begin
/end
method, or ADL-only lookup of free functionbegin
/end
, or magic for C-style array support. Note thatstd::begin
is not called unlessrange_expression
returns an object of type innamespace std
or dependent on same.In c++17 the range-for expression has been updated
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 "sentinel" which 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 achar*
. This allows a C++ range-for expression to generate optimal code when iterating over a null-terminatedchar*
buffer.live example of this.
Minimal test code is:
Here is a simple example.
Your code:
this is an example how you can augment a type you don't control to be iterable.
Here I return pointers-as-iterators, hiding the fact I have a vector under the hood.
For a type you do own, you can add methods:
here I reuse the
vector
's iterators. I useauto
for brevity; in c++11 I'd have to be more verbose.Here is a quick and dirty iterable range-view:
using c++17 template class deduction.
prints 3 4 5, skipping first 2.