static foreach

タプル章で、コンパイル時のforeachについて説明した。コンパイル時のforeachは、コンパイル時にループを反復し、各反復を個別のコードとして展開する。例えば、タプルを反復する次のforeachループがある。

auto t = tuple(42, "hello", 1.5);

foreach (i, member; t) {
    writefln("%s: %s", i, member);
}
D

コンパイラは、ループを次のような同等のコードに展開する:

{
    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);
}
D

コンパイル時のforeachは非常に強力であるが、場合によっては、その一部の特性が適さない場合もある。

void main() {
    enum arr = [1, 2];
    // 実行時に実行され、コンパイル時に展開されない:
    foreach (i; arr) {
        // ...
    }
}
D
static_foreach.1
import std.meta;

// モジュールスコープで関数オーバーロードを定義しようとしている:
foreach (T; AliasSeq!(int, double)) {    // ← コンパイルエラー
    T twoTimes(T arg) {
        return arg * 2;
    }
}

void main() {
}
D
static_foreach.2
エラー: 宣言が必要、foreachではない

static foreachは、より強力なコンパイル時機能で、より詳細な制御を提供する:

static foreach (n; FibonacciSeries().take(10).filter!isEven) {
    writeln(n);
}
D

上記のループは、以下の同等のコードに展開される:

writeln(0);
writeln(2);
writeln(8);
writeln(34);
D
import std.meta;

static foreach (T; AliasSeq!(int, double)) {
    T twoTimes(T arg) {
        return arg * 2;
    }
}

void main() {
}
D
static_foreach.3

上記のループは、以下の同等のコードに展開される:

int twoTimes(int arg) {
    return arg * 2;
}

double twoTimes(double arg) {
    return arg * 2;
}
D
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;
    }
}
D
static_foreach.4

上記のループが展開されると、switch文は次のコードと同等になる。

switch (args.length) {
case 1:
    writeln(1);
    break;

case 2:
    writeln(2);
    break;

default:
    writeln("default case");
    break;
}
D