テンプレート
I think that I can safely say that nobody understands template mechanics. -- Richard Deyman
テンプレートは、D でジェネリックプログラミングを行う方法です。 TemplateDeclaration 宣言で定義します:
TemplateDeclaration: template TemplateIdentifier ( TemplateParameterList ) { DeclDefs } TemplateIdentifier: Identifier TemplateParameterList: TemplateParameter TemplateParameter , TemplateParameterList TemplateParameter: TemplateTypeParameter TemplateValueParameter TemplateAliasParameter TemplateTupleParameter
TemplateDeclaration のボディ部は、 例えインスタンス化されることがなくても 文法的に正しいものである必要があります。意味解析はインスタンス化されるまでは 行われません。テンプレートは独自のスコープを成し、クラス、構造体、型定義、 列挙型、変数、関数、他のテンプレート、を含むことが出来ます。
テンプレートの引数は型,値,シンボル,もしくはタプルです。 型はどんな型でも構いません。 値パラメタは整数型、 浮動小数点型、文字列型のみで、 その特殊化版には整数定数、浮動小数定数、null、 もしくは文字列リテラルへ評価される式が必要です。 シンボルには任意の非ローカルシンボルを利用できます。 タプルは、型,値,シンボルの0個以上の組です。
テンプレート引数の特殊化は、 TemplateParameter が受け取れる値や型を制限します。
テンプレート引数のデフォルトは、値や型が TemplateParameter に指定されなかったときに使用されます。
明示的なテンプレートのインスタンス化
テンプレートを明示的にインスタンス化する構文は:
TemplateInstance: TemplateIdentifier !( TemplateArgumentList ) TemplateArgumentList: TemplateArgument TemplateArgument , TemplateArgumentList TemplateArgument: Type AssignExpression Symbol Symbol: SymbolTail . SymbolTail SymbolTail: Identifier Identifier . SymbolTail TemplateInstance TemplateInstance . SymbolTail
一度インスタンス化されると、 テンプレート内の宣言(テンプレートメンバ)は、 TemplateInstance のスコープへ入ります。
template TFoo(T) { alias T* t; } ... TFoo!(int).t x; // x を型 int* として宣言
テンプレートのインスタンス化はaliasで別名を付けることができます:
template TFoo(T) { alias T* t; } alias TFoo!(int) abc; abc.t x; // x を型 int* として宣言
暗黙変換前の値が同じ TemplateArgumentList で複数回 TemplateDeclaration をインスタンス化すると、同じインスタンスが参照されます。 例えば:
template TFoo(T) { T f; } alias TFoo!(int) a; alias TFoo!(int) b; ... a.f = 3; assert(b.f == 3); // a と b は TFoo の同じインスタンスを指す
これは、 テンプレートのインスタンス化が異なるモジュールで為された場合も成り立ちます。
なお、テンプレート引数が暗黙変換の効果で同じテンプレートパラメータ型に 結果として落ち着くケースは、別々のインスタンスとなります:
struct TFoo(int x) { } static assert(is(TFoo!(3) == TFoo!(2 + 1))); // 3 と 2+1 はどちらもint型の 3 static assert(!is(TFoo!(3) == TFoo!(3u))); // 3u と 3 は異なる型
複数のテンプレートが同じ TemplateIdentifier で宣言された場合、 パラメタの数が違うか、異なる特殊化がなされていれば、 違うテンプレートとして区別されます。
テンプレートの例として、ジェネリックなコピー関数があります:
template TCopy(T) { void copy(out T to, T from) { to = from; } }
このテンプレートを使うには、 まず特定の型でインスタンス化します:
int i; TCopy!(int).copy(i, 3);
インスタンス化のスコープ
TemplateInstantance は TemplateDeclaration の宣言されたスコープに あるものとして常に扱われます。テンプレート仮引数は、 パラメタから推論された型へのaliasとしてスコープに追加されます。
例えば:
module a
template TFoo(T) { void bar() { func(); } }module b
import a; void func() { } alias TFoo!(int) f; // エラー: func はモジュール a で定義されていない
そして:
module a
template TFoo(T) { void bar() { func(1); } } void func(double d) { }module b
import a; void func(int i) { } alias TFoo!(int) f; ... f.bar(); // a.func(double) を呼び出す
TemplateParameter の特殊化とデフォルト値は、 TemplateDeclaration のスコープ内で評価されます。
引数推論
テンプレートパラメタの型は、 インスタンス化時に、 対応する引数と比較することで推論されます。
各々の型パラメータについて、全てのパラメータの型が推論されるまで 以下の規則が適用されます:
- そのパラメタに対する特殊化版が存在しなければ、 パラメタ型はテンプレート引数そのものに設定されます。
- 型パラメタに依存した特殊化版があれば、 そのパラメタ内の型は、 テンプレート引数型の対応する部分へと設定されます。
- 全ての型引数を調べた後、 まだ型の決定されていないパラメタがあれば、 そのパラメタは TemplateArgumentList 中で同じ位置にある 引数型へと設定されます。
- 以上のルールで一意に定まらないパラメタがあれば、 エラーです。
例えば:
template TFoo(T) { } alias TFoo!(int) Foo1; // (1) T は int と推論される alias TFoo!(char*) Foo2; // (1) T は char* と推論される template TBar(T : T*) { } alias TBar!(char*) Foo3; // (2) T は char と推論される template TAbc(D, U : D[]) { } alias TAbc!(int, int[]) Bar1; // (2) D は int と推論される。U は int[] alias TAbc!(char, int[]) Bar2; // (4) エラー。D が char でも int でもある template TDef(D : E*, E) { } alias TDef!(int*, int) Bar3; // (1) E は int // (3) D は int*
特殊化時の推論では、 二つ以上の型パラメータを決定することも可能です:
template Foo(T: T[U], U) { ... } Foo!(int[long]) // Fooを、TをintにUをlongにしてインスタンス化
マッチを取る際には、 クラスはその基底クラスやインターフェイスにもマッチします:
class A { } class B : A { } template TFoo(T : A) { } alias TFoo!(B) Foo4; // (3) T は B template TBar(T : U*, U : A) { } alias TBar!(B*, B) Foo5; // (2) T は B* // (3) U は B
型パラメタ
TemplateTypeParameter: Identifier Identifier TemplateTypeParameterSpecialization Identifier TemplateTypeParameterDefault Identifier TemplateTypeParameterSpecialization TemplateTypeParameterDefault TemplateTypeParameterSpecialization: : Type TemplateTypeParameterDefault: = Type
特殊化
テンプレートは、 識別子 : 特殊型 という形式で、 特定の型に対して特殊化することができます。 例:
template TFoo(T) { ... } // #1 template TFoo(T : T[]) { ... } // #2 template TFoo(T : char) { ... } // #3 template TFoo(T,U,V) { ... } // #4 alias TFoo!(int) foo1; // #1 をインスタンス化 alias TFoo!(double[]) foo2; // #2 をインスタンス化。T は double alias TFoo!(char) foo3; // #3 をインスタンス化 alias TFoo!(char, int) fooe; // エラー。引数の個数が合わない alias TFoo!(char, int, int) foo4; // #4 をインスタンス化
選択されるテンプレートは、 TemplateArgumentList の型があって、 かつ最も特殊化度が高いものになります。 どれが一番特殊化されているかは、 C++ の Partial Ordering 規則と同様に決定します。あいまいな場合はエラーです。
値パラメタ
TemplateValueParameter: BasicType Declarator BasicType Declarator TemplateValueParameterSpecialization BasicType Declarator TemplateValueParameterDefault BasicType Declarator TemplateValueParameterSpecialization TemplateValueParameterDefault TemplateValueParameterSpecialization: : ConditionalExpression TemplateValueParameterDefault: = AssignExpression
テンプレートの値引数の型は、 コンパイル時に静的初期化可能な任意の型を指定可能です。 また、値としては任意のコンパイル時評価可能な式を指定可能です。 これには、整数、浮動小数点数、文字列が含まれます。
template foo(string s) { string bar() { return s ~ " betty"; } } void main() { writefln("%s", foo!("hello").bar()); // 表示: hello betty }
この例は、 値パラメタを10に特殊化したテンプレートfooの例です:
template foo(U : int, int T : 10) { U x = T; } void main() { assert(foo!(int, 10).x == 10); }
aliasパラメタ
TemplateAliasParameter: alias Identifier TemplateAliasParameterSpecializationopt TemplateAliasParameterDefaultopt alias BasicType Declarator TemplateAliasParameterSpecializationopt TemplateAliasParameterDefaultopt TemplateAliasParameterSpecialization: : Type : ConditionalExpression TemplateAliasParameterDefault: = Type = ConditionalExpression
aliasパラメタによって、テンプレートの引数に様々な D のシンボルを受け取るようにできます。alias パラメタに使えるシンボルは グローバル/ローカル名とtypedef名、テンプレート名、テンプレートインスタンスの名前です。
- グローバル名
int x; template Foo(alias X) { static int* p = &X; } void test() { alias Foo!(x) bar; *bar.p = 3; // x を 3 に設定 static int y; alias Foo!(y) abc; *abc.p = 3; // y を 3 に設定 }
- 型名
class Foo { static int p; } template Bar(alias T) { alias T.p q; } void test() { alias Bar!(Foo) bar; bar.q = 3; // Foo.p を 3 に設定 }
- モジュール名
import std.string; template Foo(alias X) { alias X.toString y; } void test() { alias Foo!(std.string) bar; bar.y(3); // std.string.toString(3) を呼ぶ }
- テンプレート名
int x; template Foo(alias X) { static int* p = &X; } template Bar(alias T) { alias T!(x) abc; } void test() { alias Bar!(Foo) bar; *bar.abc.p = 3; // x を 3 に設定 }
- テンプレートの別名
int x; template Foo(alias X) { static int* p = &X; } template Bar(alias T) { alias T.p q; } void test() { alias Foo!(x) foo; alias Bar!(foo) bar; *bar.q = 3; // x を 3 に設定 }
タプル引数
TemplateTupleParameter: Identifier ...
テンプレートの最後の引数が TemplateTupleParameter として宣言されていた場合、 その引数はテンプレートに渡された引数の末尾の列とマッチし、 その列がタプルとなります。 Tuple は、型でも、式でも、シンボルでもありません。 型と式とシンボルの混ざった列が、Tupleです。
Tuple の要素が全て型であるようなとき、 TypeTuple と呼びます。 Tuple の要素が全て式であるようなときは、 ExpressionTuple と呼びます。
Tupleは、他のテンプレートをインスタンス化するのに使ったり、 関数の引数リストとして使用したりできます。
template Print(A ...) { void print() { writefln("args are ", A); } } template Write(A ...) { void write(A a) // A は TypeTuple // a は ExpressionTuple { writefln("args are ", a); } } void main() { Print!(1,'a',6.8).print(); // args are 1a6.8 と表示 Write!(int, char, double).write(1, 'a', 6.8); // args are 1a6.8 と表示 }
タプル引数は、 関数テンプレートの暗黙のインスタンス化の際には、 末尾の引数の型から決定されます:
template Foo(T, R...) { void Foo(T t, R r) { writefln(t); static if (r.length) // さらに引数があれば、 Foo(r); // 残りについても処理をする } } void main() { Foo(1, 'a', 6.8); }
表示は以下のようになります:
1 a 6.8
タプルは、関数の引数として渡された delegate や関数の引数リストから導出することも可能です:
import std.stdio; /* R は返値型 * A は先頭の引数型 * U は残りの引数の TypeTuple */ R delegate(U) Curry(R, A, U...)(R delegate(A, U) dg, A arg) { struct Foo { typeof(dg) dg_m; typeof(arg) arg_m; R bar(U u) { return dg_m(arg_m, u); } } Foo* f = new Foo; f.dg_m = dg; f.arg_m = arg; return &f.bar; } void main() { int plus(int x, int y, int z) { return x + y + z; } auto plus_two = Curry(&plus, 2); writefln("%d", plus_two(6, 8)); // 16 と表示 }
Tupleの要素数は、 .length プロパティで取得可能です。 第 n 番目の要素は 添え字アクセス [n] で取得できますし、 部分タプルはスライスによって取り出すことができます。
Tupleは静的な、コンパイル時限定の要素です。 動的に要素の変更・追加・削除を行うことはできません。
TemplateTupleParameter なしのテンプレートと有りのテンプレートがどちらも インスタンス化にマッチする場合は、 TemplateTupleParameter のない方が優先されます。
テンプレート引数のデフォルト値
末尾のテンプレート引数にはデフォルト値を指定できます: (※訳注: Trailing template parameters。上手い訳が…)
template Foo(T, U = int) { ... } Foo!(uint,long); // Fooを、Tをuint, Uをlongとしてインスタンス化 Foo!(uint); // Fooを、Tをuint、Uをintとしてインスタンス化 template Foo(T, U = T*) { ... } Foo!(uint); // Fooを、Tをuint, Uをuint*としてインスタンス化
テンプレートの暗黙プロパティ
もしテンプレートが丁度ひとつだけメンバを持ち、 しかもその名前がテンプレートの名前と同じであれば、 そのメンバはテンプレートのインスタンス化によって参照されることになります:
template Foo(T) { T Foo; // 型Tの変数Fooを宣言 } void test() { Foo!(int) = 6; // Foo!(int).Foo の代わりに }
コンストラクタテンプレート
TemplatedConstructor:
this ( TemplateParameterList ) Parameters Constraintopt FunctionBody
テンプレートはクラスや構造体のコンストラクタを作るのにも使用できます。
クラステンプレート
ClassTemplateDeclaration: class Identifier ( TemplateParameterList ) Constraintopt BaseClassList ClassBody
もしテンプレートが丁度ひとつのメンバを持ち、 そのメンバがテンプレートと同じ名前のクラスであれば:
template Bar(T) { class Bar { T member; } }
その時は、意味的に同等な ClassTemplateDeclaration で書くことができます:
class Bar(T)
{
T member;
}
構造体、共用体、インターフェイステンプレート
StructTemplateDeclaration: struct Identifier ( TemplateParameterList ) StructBody UnionTemplateDeclaration: union Identifier ( TemplateParameterList ) StructBody InterfaceTemplateDeclaration: interface Identifier ( TemplateParameterList ) BaseInterfaceList InterfaceBody
クラステンプレートの場合と同様に、構造体、共用体、 インターフェイスもテンプレート引数リストを加えることでテンプレート化することが可能です。
関数テンプレート
テンプレートが一つしかメンバを持たず、 そのメンバがテンプレートと同じ名前を持つ関数だった場合、関数テンプレート宣言となります。 別の手段として、 実行時 Parameters の直前に TemplateParameterList を並べて関数宣言することでも、関数テンプレートを宣言できます。
T 型の二乗を計算する関数は:
T Square(T)(T t)
{
return t * t;
}
関数テンプレートは、!(TemplateArgumentList) で明示的に特殊化することも可能ですし:
writefln("The square of %s is %s", 3, Square!(int)(3));
暗黙に、実引数の型から TemplateArgumentList を推論させることも可能です:
writefln("The square of %s is %s", 3, Square(3)); // T は int と推論される
TemplateArgumentList として渡された実引数の個数が TemplateParameterList の仮引数の個数より少ない場合は、 仮引数が左から順に埋められて行き、 埋まらなかった部分が実引数の型から暗黙推論されます。
暗黙推論したい関数テンプレート引数は、 特殊化することはできません:
void Foo(T : T*)(T t) { ... } int x,y; Foo!(int*)(x); // ok, Tは関数引数から推論されているのではない Foo(&y); // エラー。T は特殊化されている
暗黙推論されるもの以外のテンプレート引数にはデフォルト値を指定できます:
void Foo(T, U=T*)(T t) { U p; ... } int x; Foo(&x); // T は int, U は int*
再帰的テンプレート
テンプレート機能は組み合わせて使うことで、 単純でない関数のコンパイル時評価など、面白い効果を生むことができます。 例えば、階乗を計算するテンプレートはこう書かれます:
template factorial(int n : 1) { enum { factorial = 1 } } template factorial(int n) { enum { factorial = n* factorial!(n-1) } } void test() { writefln("%s", factorial!(4)); // 24と表示 }
制限事項
テンプレートは、 クラスに非staticメンバや仮想関数を追加するのには使えません。 例えば:
class Foo { template TBar(T) { T xx; // Foo の static メンバとなる int func(T) { ... } // non-virtual static T yy; // Ok static int func(T t, int y) { ... } // Ok } }
関数内ではテンプレートは宣言できません。
テンプレートで interface に関数を追加することはできません。
interface TestInterface { void tpl(T)(); } // エラー