Quando il compilatore incontra un foreach
foreach (element; range)
{
// Corpo del ciclo...
}
viene internamente riscritto in modo simile a questo:
for (auto __rangeCopy = range;
!__rangeCopy.empty;
__rangeCopy.popFront())
{
auto element = __rangeCopy.front;
// Corpo del ciclo...
}
Qualsiasi oggetto che soddisfa la seguente interfaccia è chiamato range
(o più specificamente InputRange
) ed è quindi un tipo su cui si può iterare:
interface InputRange(E)
{
bool empty();
E front();
void popFront();
}
Dai un'occhiata all'esempio sulla destra per esaminare più da vicino l'implementazione e l'utilizzo di un input range.
I range sono pigri. Non verranno esaminati fino a quando non richiesto. Quindi, è possibile prendere un range da un range infinito:
42.repeat.take(3).writeln; // [42, 42, 42]
Se l'oggetto range è un tipo per valore, allora il range verrà copiato e solo la copia sarà consumata:
auto r = 5.iota;
r.drop(5).writeln; // []
r.writeln; // [0, 1, 2, 3, 4]
Se l'oggetto range è un tipo per riferimento (es. class
o std.range.refRange
),
allora il range verrà consumato e non sarà ripristinato:
auto r = 5.iota;
auto r2 = refRange(&r);
r2.drop(5).writeln; // []
r2.writeln; // []
InputRange
Copiabili sono ForwardRange
La maggior parte dei range nella libreria standard sono struct e quindi l'iterazione
foreach
è solitamente non distruttiva, anche se non garantita. Se questa
garanzia è importante, si può usare una specializzazione di un InputRange
—
i ForwardRange con un metodo .save
:
interface ForwardRange(E) : InputRange!E
{
typeof(this) save();
}
// per valore (Structs)
auto r = 5.iota;
auto r2 = refRange(&r);
r2.save.drop(5).writeln; // []
r2.writeln; // [0, 1, 2, 3, 4]
ForwardRange
possono essere estesi a range bidirezionali + range ad accesso casualeCi sono due estensioni del ForwardRange
copiabile: (1) un range bidirezionale
e (2) un range ad accesso casuale.
Un range bidirezionale permette l'iterazione dalla fine:
interface BidirectionalRange(E) : ForwardRange!E
{
E back();
void popBack();
}
5.iota.retro.writeln; // [4, 3, 2, 1, 0]
Un range ad accesso casuale ha una length
nota e ogni elemento può essere acceduto direttamente.
interface RandomAccessRange(E) : ForwardRange!E
{
E opIndex(size_t i);
size_t length();
}
Il range ad accesso casuale più conosciuto è l'array di D:
auto r = [4, 5, 6];
r[1].writeln; // 5
Le funzioni in std.range
e
std.algorithm
forniscono
i blocchi di costruzione che utilizzano questa interfaccia. I range permettono la
composizione di algoritmi complessi dietro un oggetto che
può essere iterato con facilità. Inoltre, i range permettono la creazione di oggetti
pigri che eseguono un calcolo solo quando è realmente necessario
in un'iterazione, ad esempio quando si accede al prossimo elemento del range.
Gli algoritmi speciali per i range saranno presentati più avanti nella
sezione Gemme di D.