static foreach
We saw compile-time foreach
earlier in the Tuples chapter. Compile-time foreach
iterates the loop at compile time and unrolls each iteration as separate pieces of code. For example, given the following foreach
loop over a tuple:
auto t = tuple(42, "hello", 1.5); foreach (i, member; t) { writefln("%s: %s", i, member); }
The compiler unrolls the loop similar to the following equivalent code:
{ enum size_t i = 0; int member = t[i]; writefln("%s: %s", i, member); } { enum size_t i = 1; string member = t[i]; writefln("%s: %s", i, member); } { enum size_t i = 2; double member = t[i]; writefln("%s: %s", i, member); }
Although being very powerful, some properties of compile-time foreach
may not be suitable in some cases:
- With compile-time
foreach
, each unrolling of the loop introduces a scope. As seen with thei
andmember
variables above, this allows the use of a symbol in more than one scope without causing a multiple definition error. Although this can be desirable in some cases, it makes it impossible for code unrolled for one iteration to use code from other iterations. - Compile-time
foreach
works only with tuples (including template arguments in the form ofAliasSeq
). For example, despite the elements of the following array literal being known at compile time, the loop will always be executed at run time (this may very well be the desired behavior):
void main() { enum arr = [1, 2]; // Executed at run time, not unrolled at compile time: foreach (i; arr) { // ... } }
- Like regular
foreach
, compile-timeforeach
can only be used inside functions. For example, it cannot be used at module scope or inside a user-defined type definition.
import std.meta; // Attempting to define function overloads at module scope: foreach (T; AliasSeq!(int, double)) { // ← compilation ERROR T twoTimes(T arg) { return arg * 2; } } void main() { }
Error: declaration expected, not foreach
- With compile-time
foreach
, it may not be clear whetherbreak
andcontinue
statements inside the loop body should affect the compile-time loop iteration itself or whether they should be parts of the unrolled code.
static foreach
is a more powerful compile-time feature that provides more control:
static foreach
can work with any range of elements that can be computed at compile time (including number ranges like1..10
). For example, given theFibonacciSeries
range from the Ranges chapter and a function that determines whether a number is even:
static foreach (n; FibonacciSeries().take(10).filter!isEven) {
writeln(n);
}
The loop above would be unrolled as the following equivalent:
writeln(0); writeln(2); writeln(8); writeln(34);
static foreach
can be used at module scope.static foreach
does not introduce a separate scope for each iteration. For example, the following loop defines two overloads of a function at module scope:
import std.meta; static foreach (T; AliasSeq!(int, double)) { T twoTimes(T arg) { return arg * 2; } } void main() { }
The loop above would be unrolled as its following equivalent:
int twoTimes(int arg) { return arg * 2; } double twoTimes(double arg) { return arg * 2; }
break
andcontinue
statements inside astatic foreach
loop require labels for clarity. For example, the following code unrolls (generates)case
clauses inside aswitch
statement. Thebreak
statements that are under eachcase
clause must mention the associatedswitch
statements by labels:
import std.stdio; void main(string[] args) { theSwitchStatement: switch (args.length) { static foreach (i; 1..3) { case i: writeln(i); break theSwitchStatement; } default: writeln("default case"); break; } }
After the loop above is unrolled, the switch
statement would be the equivalent of the following code:
switch (args.length) { case 1: writeln(1); break; case 2: writeln(2); break; default: writeln("default case"); break; }