Ranges

Wenn der Compiler auf ein foreach-Konstrukt trifft...

foreach (element; range)
{
    // Loop body...
}

wird es intern etwa wie folgt umgeschrieben:

for (auto __rangeCopy = range;
     !__rangeCopy.empty;
     __rangeCopy.popFront())
 {
    auto element = __rangeCopy.front;
    // Loop body...
}

Falls das Range-Objekt ein Referenztyp ist (z.B. class), wird es verbraucht und ist daher für weitere Iterationen nicht mehr verfügbar (es sei denn, der Schleifenrumpf bricht vor der letzten Iteration ab). Falls das Range-Objekt ein Werttyp ist, wird eine Kopie der Range erzeugt und - abhängig von der Definition - die ursprüngliche Range verbraucht. Die meisten Ranges der Standard-Bibliothek sind Strukturen (struct), sodass eine Iteration normalerweise nicht zerstörend wirkt - allerdings nicht garantiert. Sollte diese Garantie wichtig sein, sind forward -Ranges erforderlich.

Jedes Objekt mit folgendem Interface wird Range genannt und kann somit iteriert werden:

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

Beachte: Obwohl empty und front gewöhnlich als const-Funktionen definiert werden (was impliziert, dass ein Aufruf die Range nicht modifiziert), ist dies nicht erforderlich.

Die Funktionen in std.range und std.algorithm stellen Bausteine zur Verfügung, die dieses Interface nutzen. Ranges erlauben die einfache Erstellung komplexer iterierender Algorithmen. Auch erlauben Ranges die Erstellung von Lazy-Objekten, die ihre Berechnungen nur ausführen, wenn dies wirklich nötig ist, z.B. wenn während einer Iteration auf das nächste Range-Element zugegriffen wird.

Übungsaufgabe

Vervollständige den Quellcode, um eine FibonacciRange erzeugen, die Zahlen der Fibonacci-Folge generiert. Derassert-Befehl am Ende stellt die Korrektheit deiner Implementation sicher!

Weiterführende Quellen

rdmd playground.d