Ranges

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...
}

If the range object is a reference type (e.g. class), then the range will be consumed and won't available for subsequent iteration (that is unless the loop body breaks before the last loop iteration). If the range object is a value type, then a copy of the range will be made and depending on the way the range is defined the loop may or may not consume the original range. 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, require forward ranges.

Any object which fulfills the above interface is called a range and is thus a type that can be iterated over:

    struct Range
    {
        @property bool empty() const;
        void popFront();
        T front() const;
    }

Note that while it is customary for empty and front to be defined as const functions (implying that calling them won't modify the range), this is not required.

The functions in std.range and std.algorithm provide building blocks that make use of this interface. Ranges allow us to compose complex algorithms behind an object that can be iterated with ease. Furthermore, ranges allow us to create 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.

Exercise

Complete the source code to create the FibonacciRange range that returns numbers of the Fibonacci sequence. Don't fool yourself into deleting the assertions!

In-depth

rdmd playground.d

{{programOutput}}