英語版
このページの英語版を見る

コンパイル時シーケンス

背景

コンパイル時シーケンスは、メタプログラミングの重要な概念であり、 D言語の"可変長のテンプレート"サポートから自然に派生したものである。 これによって、プログラマーは型、シンボル、値を操作することが可能になり、 型、シンボル、値を操作するコンパイル時アルゴリズムを定義できるようになる。

注釈: 歴史的な理由により、これらのシーケンスは文書やコンパイラの内部ではタプルと呼ばれることがあるが、 混同しないように。これらは、他の言語で一般的に存在するタプルとはあまり共通点がない。 異なる型の値のシーケンスは、 std.typecons.Tuple。 「タプル」という用語をコンパイル時のシーケンスを意味するものとして使用することは、混乱を避けるため推奨されない。 もし遭遇した場合は、

この単純な可変長テンプレートを考えてみよう:

template Variadic(T...) { /* ... */ }

T これは、TemplateParameterSequenceであり、 これは言語のコア機能である。これは独自の特別な意味論を持ち、 プログラマーの視点では、コンパイル時のエンティティ("型"、"シンボル(名前)"、"値")の配列に最も似ている。このシーケンスの長さを確認し、 個々の要素にアクセスすることができる。

template Variadic(T...)
{
    static assert(T.length > 1);
    pragma(msg, T[0]);
    pragma(msg, T[1]);
}

alias dummy = Variadic!(int, 42);

コンパイル中の出力:

int
42

AliasSeq

言語自体は、テンプレートパラメータ宣言以外の方法でこのようなシーケンスを定義する手段を提供していない。 その代わりに、 標準ライブラリが提供するシンプルなテンプレートがある。

alias AliasSeq(T...) = T;

これは、特定の可変長引数シーケンスに外部からアクセス可能な名前を付与し、 他のコンテキストでも利用できるようにするものである。

import std.meta;
// 他の名前にエイリアスを付けることができる
alias Name = AliasSeq!(int, 42);
pragma(msg, Name[0]);   // int
pragma(msg, Name[1]);   // 42
// またはシーケンスを直接操作する
pragma(msg, AliasSeq!("aaa", 0, double)[2]);    // double

利用可能な操作

長さの確認

import std.meta;
static assert(AliasSeq!(1, 2, 3, 4).length == 4);

インデックス付けとスライス

インデックスはコンパイル時に指定する必要がある

import std.meta;

alias nums = AliasSeq!(1, 2, 3, 4);
static assert(nums[1] == 2);

// 最後の3つの要素をスライスする
alias tail = nums[1 .. $];
static assert(tail[0] == 2);
static assert(tail.length == 3);

要素の代入

シーケンス要素が変更可能な変数を参照するシンボルである場合のみ動作する

import std.meta;

void main()
{
    int x;
    alias seq = AliasSeq!(10, x);
    seq[1] = 42;
    assert(x == 42);
    // seq[0] = 42; // コンパイルできない、定数に代入できない
}

foreach

Dのforeach文は、 コンパイル時のシーケンスを反復処理する場合に特別な意味を持つ。ループの本体を シーケンス要素ごとに繰り返し、ループ反復シンボルは

import std.meta;

void main()
{
    foreach (sym; AliasSeq!(int, "literal", main))
    {
        static if (is(sym))
            pragma (msg, sym);
        else
            pragma (msg, typeof(sym));
    }
}

コンパイル時の出力:

int
string
void()

注釈: 新しいコードでは、静的 foreach を使用することを推奨する。

自動展開

コンパイル時の引数シーケンスのあまり明白ではない特性のひとつとして、 関数やテンプレートの引数として使用された場合、それらは自動的にカンマ区切りの引数シーケンスとして扱われる というものがある。

import std.meta;

template Print0(T...)
{
    pragma(msg, T[0]);
}

alias Dummy = Print0!(AliasSeq!(int, double));

これは、最後の行が alias Dummy = Print0!(int, double) として書き換えられるため、コンパイル時にのみint と表示される。自動展開が行われなかった場合、 代わりにAliasSeq!(int, double) と表示される。これは、 可変長シーケンスに関する言語セマンティクスの本質的な部分であり、AliasSeq によっても保持される。

同種シーケンス

型要素または値要素のみで構成されるエイリアスシーケンスは、 それぞれ一般的に「型シーケンス」または「値シーケンス」と呼ばれる。 「シンボルシーケンス」という概念はあまり使用されないが、同じパターンに当てはまる。

型シーケンス

宣言では、同種の型シーケンスを使用することが可能である。

import std.meta;
alias Params = AliasSeq!(int, double, string);
void foo(Params); // void foo(int, double, string);

foo(7, 6.5, "hi");

型シーケンスのインスタンス化

Dは、型シーケンスが型として機能する特別な変数宣言構文をサポートしている。

import std.meta;

void foo()
{
    AliasSeq!(int, double, string) variables;
    variables[0] = 42;
    variables[1] = 42.0;
    variables[2] = "just a normal variable";
}

コンパイラは、このような宣言を以下のように書き換える。

int __var1;
double __var2;
string __var3;
alias variables = AliasSeq!(__var1, __var2, __var3);

つまり、型シーケンスのインスタンスは、 変数エイリアスだけを持つシンボルシーケンスの一種であり、"l値"シーケンスとして知られている。

可変長引数テンプレート関数は、 型シーケンスのインスタンスを使用して関数のパラメータを宣言する。

void foo(T...)(T args)
{
    // 'T'は引数の型列である。
    // 'args'はTのインスタンスで、その要素は関数のパラメータである
    static assert(is(typeof(args) == T));
    args[0] = T[0].init;
}

値のシーケンス

同じ型の値のシーケンスを使用して配列リテラルを宣言することも可能である。

import std.meta;
enum e = 3;
enum arr = [ AliasSeq!(1, 2, e) ];
static assert(arr == [ 1, 2, 3 ]);

上記のシーケンスは、リテラル値と明示的定数のみで構成されているため、"r値"シーケンスである。 各要素の値はコンパイル時に既知である。

以下は、"l値" シーケンスの使用例である。

import std.meta, std.algorithm;
auto x = 3, y = 7;
alias pair = AliasSeq!(x, y);
swap(pair);
assert(x == 7 && y == 3);

上記のように、このようなシーケンスでは 要素の代入を使用できる。

.tupleofクラス/構造体のインスタンスプロパティは、 各フィールドの"l値"シーケンスを提供する。

関数は値シーケンスを返すことはできない。代わりに、 std.typecons.Tupleを使用することができます。これは、l値シーケンスを 構造体でラップし、自動展開を防止します。

シンボルシーケンス

シンボルシーケンスは、任意の名前のシンボル(型、変数、関数、テンプレート)のエイリアスとなるが、 リテラルはエイリアスとならない。 エイリアスシーケンスと同様に、要素の種類を混在させることができる。

import std.meta : AliasSeq;

void f(T)(T v)
{
    pragma(msg, T);
}
alias syms = AliasSeq!(f, f!byte);
syms[0](42); // IFTIでf!intを呼び出す
syms[1](42); // f!byteを呼び出す

コンパイル時の出力:

byte
int

関数f!byte は、シーケンスが作成されたときにインスタンス化され、 呼び出し側ではインスタンス化されないことに注意。