If a foreach
is encountered by the compiler
foreach (element; range)
{
// Loop body...
}
it's internally rewritten similar to the following:
for (auto __rangeCopy = range;
!__rangeCopy.empty;
__rangeCopy.popFront())
{
auto element = __rangeCopy.front;
// Loop body...
}
Any object which fulfills the following interface is called a range
(or more specific InputRange
) and is thus a type that can be iterated over:
interface InputRange(E)
{
bool empty();
E front();
void popFront();
}
Have a look at the example on the right to inspect the implementation and usage of an input range closer.
Ranges are lazy. They won't be evaluated until requested. Hence, a range from an infinite range can be taken:
42.repeat.take(3).writeln; // [42, 42, 42]
If the range object is a value type, then range will be copied and only the copy will be consumed:
auto r = 5.iota;
r.drop(5).writeln; // []
r.writeln; // [0, 1, 2, 3, 4]
If the range object is a reference type (e.g. class
or std.range.refRange
),
then the range will be consumed and won't be reset:
auto r = 5.iota;
auto r2 = refRange(&r);
r2.drop(5).writeln; // []
r2.writeln; // []
InputRanges
are ForwardRanges
Most of the ranges in the standard library are structs and so foreach
iteration is usually non-destructive, though not guaranteed. If this
guarantee is important, a specialization of an InputRange
can be used—
forward ranges with a .save
method:
interface ForwardRange(E) : InputRange!E
{
typeof(this) save();
}
// by value (Structs)
auto r = 5.iota;
auto r2 = refRange(&r);
r2.save.drop(5).writeln; // []
r2.writeln; // [0, 1, 2, 3, 4]
ForwardRanges
can be extended to Bidirectional ranges + random access rangesThere are two extensions of the copyable ForwardRange
: (1) a bidirectional range
and (2) a random access range.
A bidirectional range allows iteration from the back:
interface BidirectionalRange(E) : ForwardRange!E
{
E back();
void popBack();
}
5.iota.retro.writeln; // [4, 3, 2, 1, 0]
A random access range has a known length
and each element can be directly accessed.
interface RandomAccessRange(E) : ForwardRange!E
{
E opIndex(size_t i);
size_t length();
}
The best known random access range is D's array:
auto r = [4, 5, 6];
r[1].writeln; // 5
The functions in std.range
and
std.algorithm
provide
building blocks that make use of this interface. Ranges enable the
composition of complex algorithms behind an object that
can be iterated with ease. Furthermore, ranges enable the creation of lazy
objects that only perform a calculation when it's really needed
in an iteration e.g. when the next range's element is accessed.
Special range algorithms will be presented later in the
D's Gems section.