テンプレート
テンプレート宣言
テンプレートは、D言語のジェネリックプログラミングへのアプローチである。 テンプレートは、TemplateDeclarationで定義できる。
TemplateDeclaration: template Identifier TemplateParameters Constraintopt { DeclDefsopt } TemplateParameters: ( TemplateParameterListopt ) TemplateParameterList: TemplateParameter TemplateParameter , TemplateParameter , TemplateParameterList
テンプレートのDeclDefs本体は、たとえインスタンス化されない場合でも、文法的に正しいものでなければならない。 意味解析はインスタンス化されるまで行われない。 テンプレートは独自のスコープを形成し、テンプレート本体には、クラス、構造体、型、 列挙型、変数、関数、および他のテンプレートなどの宣言を含めることができる。
テンプレートパラメータには、"型"、" 値"、"シンボル"、または"シーケンス"を指定できる。
template t(T) // 型パラメータTを宣言する { T v; // テンプレートtの中でT型のメンバ変数を宣言する }
テンプレートパラメータは、 テンプレートパラメータが受け入れる引数を制限する特殊化を持つことができる。
template t(T : int) // T型は暗黙のうちにint型に変換しなければならない { ... }
同じ識別子を持つ複数のテンプレートが宣言されている場合、 パラメータが異なっていたり、特殊化が異なっていたりすれば、それらは別個のものである 。
テンプレートが、テンプレートと同じ識別子を持つメンバを持つ場合、 そのテンプレートは 「エポニムテンプレート」である。 template 宣言で、エポニムメンバが1つだけの場合、通常は 代わりに特定の短い構文の テンプレート宣言として記述される。
templatetemplateテンプレートのインスタンス化
テンプレートは使用前にインスタンス化されなければならない。これは、 テンプレートに引数リストを渡すことを意味する。 これらの引数は通常、 テンプレート本体に置換され、新しいスコープ付きのエンティティとなる。
関数テンプレートは、 コンパイラが関数呼び出しからテンプレート引数を推論できる場合、暗黙的にインスタンス化することができる。 そうでない場合は、テンプレートを明示的にインスタンス化する必要がある。
明示的なテンプレートインスタンス化
テンプレートは、テンプレート名の後に! を使用して明示的にインスタンス化される。 その後、引数リストまたは単一のトークン引数 が続く。
TemplateInstance: Identifier TemplateArguments TemplateArguments: ! ( TemplateArgumentListopt ) ! TemplateSingleArgument TemplateArgumentList: TemplateArgument TemplateArgument , TemplateArgument , TemplateArgumentList TemplateSingleArgument: Identifier FundamentalType CharacterLiteral StringLiteral InterpolationExpressionSequence IntegerLiteral FloatLiteral true false null this SpecialKeyword
テンプレート引数は、型、コンパイル時の式、 またはシンボルである。
TemplateArgument: Type AssignExpression Symbol Symbol: SymbolTail . SymbolTail SymbolTail: Identifier Identifier . SymbolTail TemplateInstance TemplateInstance . SymbolTail
テンプレートがインスタンス化されると、テンプレート内の宣言は テンプレートメンバーと呼ばれ、 TemplateInstanceのスコープ内にある
template TFoo(T) { alias Ptr = T*; } ... TFoo!(int).Ptr x; // xをint*型と宣言する
TemplateArgumentが1トークン長の場合、括弧は省略できる。
TFoo!int.Ptr x; // TFoo!(int).Ptr x;と同じである
テンプレートのインスタンス化にはエイリアスを使用できる:
template TFoo(T) { alias Ptr = T*; } alias foo = TFoo!(int); foo.Ptr x; // xをint*型と宣言する
一般的なインスタンス化
同じ TemplateArgumentListを持つTemplateDeclaration の複数のインスタンス化は、 すべて同じインスタンスを参照する。 例えば:
template TFoo(T) { T f; } alias a = TFoo!(int); alias b = TFoo!(int); ... a.f = 3; assert(b.f == 3); // aとbはTFooの同じインスタンスを参照する
これは、テンプレートインスタンスが異なるモジュールで実行されている場合でも同様である。
テンプレート引数が暗黙のうちに同じテンプレートパラメータ型に変換されたとしても、 それらは依然として同じインスタンスを参照する。 この例では、TemplateValueParameterとstruct テンプレートを使用している。
struct TFoo(int x) { } // 異なるテンプレート・パラメータは異なる構造体タイプを作る static assert(!is(TFoo!(3) == TFoo!(2))); // 3と2+1はどちらもint型の3であり - 同じTFooインスタンスである static assert(is(TFoo!(3) == TFoo!(2 + 1))); // 3uはintパラメータと一致するように暗黙的に3に変換され、 // TFoo!(3)と全く同じインスタンスを参照する static assert(is(TFoo!(3) == TFoo!(3u)));
実用例
シンプルな一般的なコピーテンプレートは以下のようになる。
template TCopy(T) { void copy(out T to, T from) { to = from; } }
このテンプレートを使用するには、まず特定の型でインスタンス化する必要がある。
int i; TCopy!(int).copy(i, 3);
関数テンプレートも参照。
インスタンス化の範囲
テンプレートインスタンスは、常に テンプレート宣言が宣言されたスコープ内でインスタンス化され、 推論された型に対するエイリアスとして宣言されたテンプレートパラメータが追加される。
module a:Example:
モジュール a:template TFoo(T) { void bar() { func(); } }モジュールb:
import a; void func() { } alias f = TFoo!(int); // エラー: モジュールaでfuncが定義されていない
Example:
モジュール a:template TFoo(T) { void bar() { func(1); } } void func(double d) { }モジュールb:
import a; void func(int i) { } alias f = TFoo!(int); ... f.bar(); // will call a.func(double)
テンプレートパラメータの特殊化とデフォルト 引数は、テンプレート宣言のスコープ内で評価される。
テンプレートパラメータ
TemplateParameter: TemplateTypeParameter TemplateValueParameter TemplateAliasParameter TemplateSequenceParameter TemplateThisParameter
テンプレートパラメータは、"型"、"値"、"シンボル"、または"シーケンス"を取ることができる。
- 型パラメータは、任意の型を取ることができる。
- 値パラメータは、コンパイル時に静的に評価できる任意の式を取ることができる。
- エイリアス引数は、ほとんどすべてのシンボルを取ることができる。
- シーケンスパラメータは、0 個以上の型、値、またはシンボルを取ることができる。
既定引数は、 一致する引数が指定されていない場合に テンプレートパラメータで使用する型、値、またはシンボルを指定する。
型パラメータ
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 foo1 = TFoo!(int); // インスタンス化 #1 alias foo2 = TFoo!(double[]); // instantiates #2 matching pattern T[] with T being double alias foo3 = TFoo!(char); // インスタンス化 #3 alias fooe = TFoo!(char, int); // エラー、引数の数が不一致 alias foo4 = TFoo!(char, int, int); // インスタンス化 #4
インスタンス化のために選択されるテンプレートは、最も特化されたものであり、 TemplateArgumentListの型に適合するものである。 結果が曖昧である場合、エラーとなる。
型パラメータ推論
テンプレートパラメータの型は、 特定のテンプレートインスタンスに対して、テンプレート引数と 対応するテンプレートパラメータを比較することで
各テンプレートパラメータに対して、以下のルールが適用される。 各パラメータに対して型が推論されるまで、順番に適用される。
- パラメータに型指定がない場合、 パラメータの型はテンプレート引数に設定される。
- 型特殊化が型パラメータに依存している場合、 そのパラメータの型は、型引数の対応する部分に設定される。
- すべての型引数が調べられた後で、 型が割り当てられていない型パラメータが残っている場合、それらには TemplateArgumentListの同じ位置にあるテンプレート引数に対応する型が割り当てられる
- 上記のルールを適用しても、各テンプレートパラメータに正確に1つの型が割り当てられない場合は、 エラーとなる。
例えば:
template TFoo(T) { } alias foo1 = TFoo!(int); // (1) Tはintであると推測される alias foo2 = TFoo!(char*); // (1) Tはchar*であると推測される template TBar(T : T*) { } // テンプレート引数をT*パターンにマッチさせる alias bar = TBar!(char*); // (2) Tはcharであると推測される template TAbc(D, U : D[]) { } // D[]はマッチするパターンである alias abc1 = TAbc!(int, int[]); // (2) Dはint、Uはint[]と推論される alias abc2 = TAbc!(char, int[]); // (4) エラー、Dはcharとintの両方である template TDef(D : E*, E) { } // E*はマッチするパターンである alias def = TDef!(int*, int); // (1) Eはintである // (3) Dはint*である
専門分野からの控除は、 複数のパラメータに"値"を提供できる。
template Foo(T: T[U], U) { ... } Foo!(int[long]) // Tをint、Uをlongに設定してFooをインスタンス化する
一致を考慮する際、クラスは そのクラスが属するすべてのスーパークラスまたはインターフェースと一致するとみなされる。
class A { } class B : A { } template TFoo(T : A) { } alias foo = TFoo!(B); // (3) TはBである template TBar(T : U*, U : A) { } alias bar = TBar!(B*, B); // (2) TはB*である // (3) UはBであるこのパラメータ
このパラメータ
TemplateThisParameter: this TemplateTypeParameter
TemplateThisParametersは、メンバ関数のテンプレートで、 this参照の型を取得するために使用される。また、this 参照の変更可能性も推論する。例えば、this がconst の場合、関数はconst とマークされる。
struct S { void foo(this T)() const { pragma(msg, T); } } void main() { const(S) s; (&s).foo(); S s2; s2.foo(); immutable(S) s3; s3.foo(); }
const(S) S immutable(S)
実行時の型チェックを回避する
TemplateThisParameterは継承と併用すると特に有用である。例えば、 派生クラス型を返す最終ベースメソッドの実装を考えてみよう。 通常、これはベース型を返すが、
interface Addable(T) { final auto add(T t) { return this; } } class List(T) : Addable!T { List remove(T t) { return this; } } void main() { auto list = new List!int; list.add(1).remove(1); // エラー: Addable!intの'remove'メソッドがない }
ここで、add メソッドは基本型を返す。この基本型は、remove メソッドを実装していない。 この目的には、テンプレートthis パラメータを使用できる。
interface Addable(T) { final R add(this R)(T t) { return cast(R)this; // キャストは必要だが安全 } } class List(T) : Addable!T { List remove(T t) { return this; } } void main() { auto list = new List!int; static assert(is(typeof(list.add(1)) == List!int)); list.add(1).remove(1); // OK、List.add Addable!int a = list; // a.addはAddable.addを呼び出す static assert(is(typeof(a.add(1)) == Addable!int)); }
値パラメータ
TemplateValueParameter: BasicType Declarator BasicType Declarator TemplateValueParameterSpecialization BasicType Declarator TemplateValueParameterDefault BasicType Declarator TemplateValueParameterSpecialization TemplateValueParameterDefault TemplateValueParameterSpecialization: : ConditionalExpression TemplateValueParameterDefault: = AssignExpression = SpecialKeyword
テンプレート値パラメータは、コンパイル時に静的に評価できる任意の式の引数を取ることができる。 テンプレート値引数は、整数、浮動小数点数、 null、文字列、テンプレート値引数の配列リテラル、 テンプレート値引数の連想配列リテラル、 テンプレート値引数の構造体リテラルとすることができる。
template foo(string s) { string bar() { return s ~ " betty"; } } void main() { import std.stdio; writeln(foo!("hello").bar()); // 表示: hello betty }
特殊化
指定する特殊化またはデフォルト式は、コンパイル時に評価可能でなければならない。
この例では、テンプレートfoo は値パラメータを持ち、 これは10 用に特殊化されている。
template foo(U : int, int v : 10) { U x = v; } void main() { assert(foo!(int, 10).x == 10); static assert(!__traits(compiles, foo!(int, 11))); }
これは、特定の値に対して異なるテンプレート本文が必要な場合に役立つ。 別のテンプレートオーバーロードが、他の整数リテラル値を取るように定義される。
エイリアスパラメータ
TemplateAliasParameter: alias Identifier TemplateAliasParameterSpecializationopt TemplateAliasParameterDefaultopt alias BasicType Declarator TemplateAliasParameterSpecializationopt TemplateAliasParameterDefaultopt TemplateAliasParameterSpecialization: : Type : ConditionalExpression TemplateAliasParameterDefault: = Type = ConditionalExpression
エイリアス・パラメータを使用すると、シンボル名またはコンパイル時に計算された値でテンプレートをパラメータ化できる。 ほとんどすべての種類のDシンボルを使用でき、型名、 グローバル名、ローカル名、モジュール名、テンプレート名、および テンプレート・インスタンスが含まれる。 xml-ph-0000@deepl.internal
シンボルエイリアス
- 型名
class Foo { static int x; } template Bar(alias a) { alias sym = a.x; } void main() { alias bar = Bar!(Foo); bar.sym = 3; // Foo.xを3に設定する assert(Foo.x == 3); }
- グローバル名
shared int x; template Foo(alias var) { auto ptr = &var; } void main() { alias bar = Foo!(x); *bar.ptr = 3; // xを3に設定する assert(x == 3); static shared int y; alias abc = Foo!(y); *abc.ptr = 3; // yを3に設定する assert(y == 3); }
- ローカル名
template Foo(alias var) { void inc() { var++; } } void main() { int v = 4; alias foo = Foo!v; foo.inc(); assert(v == 5); }
- モジュール名
import std.conv; template Foo(alias a) { alias sym = a.text; } void main() { alias bar = Foo!(std.conv); string s = bar.sym(3); // std.conv.text(3)を呼び出す assert(s == "3"); }
- テンプレート名
shared int x; template Foo(alias var) { auto ptr = &var; } template Bar(alias Tem) { alias instance = Tem!(x); } void main() { alias bar = Bar!(Foo); *bar.instance.ptr = 3; // xを3に設定する assert(x == 3); }
- テンプレートインスタンス名
shared int x; template Foo(alias var) { auto ptr = &var; } template Bar(alias sym) { alias p = sym.ptr; } void main() { alias foo = Foo!(x); alias bar = Bar!(foo); *bar.p = 3; // xを3に設定する assert(x == 3); }
エイリアス
- リテラル
template Foo(alias x, alias y) { static int i = x; static string s = y; } void main() { import std.stdio; alias foo = Foo!(3, "bar"); writeln(foo.i, foo.s); // 3barを表示する }
- コンパイル時の値
template Foo(alias x) { static int i = x; } void main() { // コンパイル時の引数評価 enum two = 1 + 1; alias foo = Foo!(5 * two); assert(foo.i == 10); static assert(foo.stringof == "Foo!10"); // コンパイル時の関数評価 int get10() { return 10; } alias bar = Foo!(get10()); // barはfooと同じテンプレート・インスタンスである assert(&bar.i is &foo.i); }
- 関数リテラル
template Foo(alias fun) { enum val = fun(2); } alias foo = Foo!((int x) => x * x); static assert(foo.val == 4);
型付きエイリアス パラメータ
エイリアス・パラメータも型指定できる。 これらのパラメータは、その型のシンボルを受け入れる。
template Foo(alias int p) { alias a = p; } void fun() { int i = 0; Foo!i.a++; // OK assert(i == 1); float f; //Foo!f; // インスタンス化に失敗する }
専門分野
エイリアスパラメータはリテラルとユーザー定義の型シンボルの両方を受け入れることができるが、 型パラメータと値パラメータへのマッチングほどには特化されていない。 :
template Foo(T) { ... } // #1 template Foo(int n) { ... } // #2 template Foo(alias sym) { ... } // #3 struct S {} int var; alias foo1 = Foo!(S); // インスタンス化 #1 alias foo2 = Foo!(1); // インスタンス化 #2 alias foo3a = Foo!([1,2]); // インスタンス化 #3 alias foo3b = Foo!(var); // インスタンス化 #3
template Bar(alias A) { ... } // #4 template Bar(T : U!V, alias U, V...) { ... } // #5 class C(T) {} alias bar = Bar!(C!int); // インスタンス化 #5
シーケンスパラメータ
TemplateSequenceParameter: Identifier ...
TemplateParameterListの最後のテンプレートパラメータが TemplateSequenceParameterとして宣言されている場合、 それはゼロ個以上の末尾のテンプレート引数と一致する。 TemplateAliasParameterに渡すことができる引数はすべて、 シーケンスパラメータに渡すことができる。
このような引数のシーケンスは、テンプレート外で使用するためにエイリアス化できる。 std.meta.AliasSeqテンプレートは単に そのシーケンスパラメータをエイリアスするだけである。
alias AliasSeq(Args...) = Args;
TemplateSequenceParameterは、今後は
明確にするためにその名前で参照される。
AliasSeqはそれ自体は型、値、シンボルではない。これは
コンパイル時のシーケンスで
あり、 今後は、 わかりやすくするために、TemplateSequenceParameterをその名前で参照する。 AliasSeqはそれ自体は型でも値でもシンボルでもない。 コンパイル時に、 型、値、シンボルの任意の組み合わせ、またはそのいずれでもないものを
AliasSeqの要素は、 宣言または式で参照された際に自動的に展開される。 AliasSeq は、 引数として使用して テンプレートをインスタンス化することができる。
均質なシーケンス
- 要素がすべて型で構成されるAliasSeqは、 型シーケンスまたはTypeSeq と呼ばれる。
- 要素がすべて値で構成されるAliasSeqは、 値シーケンスまたはValueSeq と呼ばれる。
- typeof ValueSeqで使用することで、TypeSeqを取得できる。
ValueSeqは、関数を呼び出す引数として使用できる 。
import std.stdio : writeln; template print(args...) // argsはValueSeqでなければならない { void print() { writeln("args are ", args); } } void main() { print!(1, 'a', 6.8)(); // 表示: args are 1a6.8 }
TypeSeqは、関数のパラメータシーケンスを宣言するために使用できる。
import std.stdio : writeln; template print(Types...) // 型はTypeSeqでなければならない { void print(Types args) // argsはValueSeqである { writeln("args are ", args); } } void main() { print!(int, char, double)(1, 'a', 6.8); // 表示: args are 1a6.8 }
値シーケンス:
- コピー可能
- アドレスを取得することはできない
- 関数から返すことはできない。代わりに、 std.typecons.Tuple
シーケンス
TypeSeqは同様に、 変数を宣言する ために使用できる。TypeSeqを型とする変数は、 lvalue sequenceと呼ばれる。
l値シーケンスは、互換性のある型の値シーケンスから初期化したり、値シーケンスに代入したり、値シーケンスと比較したりすることができる。
import std.meta: AliasSeq; // 便宜上、型のエイリアスを使う alias TS = AliasSeq!(string, int); TS tup; // l値シーケンス assert(tup == AliasSeq!("", 0)); // TS.init int i = 5; // 値とシンボルのシーケンスから別のl値シーケンスを初期化する auto tup2 = AliasSeq!("hi", i); // iの値がコピーされる i++; enum hi5 = AliasSeq!("hi", 5); // r値シーケンス static assert(is(typeof(hi5) == TS)); assert(tup2 == hi5); // l値シーケンスの要素を変更できる tup = tup2; assert(tup == hi5);
- .tupleof クラスまたは構造体のインスタンスで使用すると、そのフィールドの"l値"シーケンスを取得できる。
- .tupleof 静的配列インスタンスで使用すると、その要素の"l値"シーケンスを取得できる。
l値シーケンスは、単一の式から初期化することができる。 各要素は、与えられた式から初期化される。
import std.meta: AliasSeq; AliasSeq!(int, int, int) vs = 4; assert(vs == AliasSeq!(4, 4, 4)); int[3] sa = [1, 2, 3]; vs = sa.tupleof; assert(vs == AliasSeq!(1, 2, 3));
シーケンス操作
- AliasSeqの要素数は、.length プロパティで取得できる。
- n番目の要素は、 AliasSeqを Seq[n] でインデックス化することで取得できる。インデックスはコンパイル時に既知でなければならない。 要素が変数に解決されるシンボルである場合、またはシーケンスが"l値"シーケンスである場合、結果は"l値"となる。
- スライス は、元のシーケンスの要素のサブセットからなる新しいシーケンスを生成する。
import std.meta : AliasSeq; int v = 4; // 3つの値と1つのシンボルからなるシーケンスのエイリアスを作成する alias nums = AliasSeq!(1, 2, 3, v); static assert(nums.length == 4); static assert(nums[1] == 2); //nums[0]++; // エラー、nums[0]はr値である nums[3]++; // OK、nums[3]はl値であるvにバインドされている assert(v == 5); // 最初の3つの要素をスライスする alias trio = nums[0 .. $-1]; // 配列リテラルに展開する static assert([trio] == [1, 2, 3]);
AliasSeqsは静的なコンパイル時のエンティティであり、 コンパイル時または実行時に動的に要素を変更、追加、または削除する方法はない。 代わりに、以下のいずれかを行う。
- 元のシーケンス(またはその一部)と、その前後に追加する要素を使用して、新しいシーケンスを構築する。
- エイリアス代入を使用して 新しいシーケンスを反復的に構築する。
シーケンスは、foreach 文を使用して、各要素のコードを「展開」することができる。
型シーケンス推論
型シーケンスは、暗黙的にインスタンス化された関数テンプレートの末尾パラメータから推論できる。
import std.stdio; template print(T, Args...) { void print(T first, Args args) { writeln(first); static if (args.length) // さらに引数がある場合 print(args); // 残りの引数を再帰検索する } } void main() { print(1, 'a', 6.8); }
1 a 6.8
型シーケンスは、関数引数として渡されたデリゲートまたは関数パラメータリストの型から推測することもできる。
import std.stdio; /* デリゲートの第一引数を特定の値に結びつけることによって、デリゲートを部分的に適用する。 * R = 返却値の型 * T = 第1引数の型 * Args = 残りの引数の型のTypeSeq */ R delegate(Args) partial(R, T, Args...)(R delegate(T, Args) dg, T first) { // クロージャを返す return (Args args) => dg(first, args); } void main() { int plus(int x, int y, int z) { return x + y + z; } import std.stdio; auto plus_two = partial(&plus, 2); writeln(plus_two(6, 8)); // 16を表示する }
特殊化
シーケンスパラメータ付きテンプレートとシーケンスパラメータなしテンプレートが テンプレートインスタンスに完全に一致する場合、 TemplateSequenceParameterなしのテンプレートが選択される。
template Foo(T) { pragma(msg, "1"); } // #1 template Foo(int n) { pragma(msg, "2"); } // #2 template Foo(alias sym) { pragma(msg, "3"); } // #3 template Foo(Args...) { pragma(msg, "4"); } // #4 import std.stdio; // 単独のテンプレート引数は決して#4にマッチしない alias foo1 = Foo!(int); // インスタンス化 #1 alias foo2 = Foo!(3); // インスタンス化 #2 alias foo3 = Foo!(std); // インスタンス化 #3 alias foo4 = Foo!(int, 3, std); // インスタンス化 #4
デフォルトの引数
末尾のテンプレートパラメータにはデフォルト引数を指定できる。
template Foo(T, U = int) { ... } Foo!(uint,long); // Tをuint、UをlongとしてFooをインスタンス化する Foo!(uint); // Tをuint、UをintとしてFooをインスタンス化する template Foo(T, U = T*) { ... } Foo!(uint); // Tをuint、Uをuint*としてFooをインスタンス化する
参照:関数テンプレートのデフォルト引数。
同名のテンプレート
テンプレートに、テンプレート識別子と同じ名前のメンバーが含まれている場合、 これらのメンバーはテンプレートインスタンス化で参照されていると想定される。
template foo(T) { T foo; // T型の変数fooを宣言する } void main() { foo!(int) = 6; // 代わりにfoo!(int).fooを宣言する }
以下の例では、同名のメンバが複数あり、 「暗黙の関数テンプレート化」を使用している。
template foo(S, T) { // 各メンバーはすべてのテンプレート・パラメーターを含む void foo(S s, T t) {} void foo(S s, T t, string) {} } void main() { foo(1, 2, "test"); // foo!(int, int).foo(1, 2, "test") foo(1, 2); // foo!(int, int).foo(1, 2) }
集約型テンプレート
ClassTemplateDeclaration: class Identifier TemplateParameters ; class Identifier TemplateParameters Constraintopt BaseClassListopt AggregateBody class Identifier TemplateParameters BaseClassListopt Constraintopt AggregateBody InterfaceTemplateDeclaration: interface Identifier TemplateParameters ; interface Identifier TemplateParameters Constraintopt BaseInterfaceListopt AggregateBody interface Identifier TemplateParameters BaseInterfaceList Constraint AggregateBody StructTemplateDeclaration: struct Identifier TemplateParameters ; struct Identifier TemplateParameters Constraintopt AggregateBody UnionTemplateDeclaration: union Identifier TemplateParameters ; union Identifier TemplateParameters Constraintopt AggregateBody
テンプレートがメンバーを正確に1つだけ宣言しており、そのメンバーが テンプレートと同じ名前のクラスである場合( 同名のテンプレートを参照)は、
template Bar(T) { class Bar { T member; } }その意味は、ClassTemplateDeclarationと呼ばれる 宣言として次のように書くことができる。
class Bar(T)
{
T member;
}
クラステンプレートと同様に、構造体、共用体、インターフェースは、 テンプレートパラメータリストを指定することでテンプレートに変換できる。
関数テンプレート
テンプレートが正確に1つのメンバを宣言し、そのメンバが テンプレートと同じ名前の関数である場合、それは関数テンプレート宣言である。 あるいは、関数テンプレート宣言は、 Parametersの直前にTemplateParameterListを伴う関数宣言である 。
型 T の平方を計算する関数テンプレートは以下のとおりである。
T square(T)(T t)
{
return t * t;
}
次のように下位互換となる。
template square(T) { T square(T t) { return t * t; } }
関数テンプレートは、識別子を使って明示的にインスタンス化することができる。!(TemplateArgumentList):
writefln("The square of %s is %s", 3, square!(int)(3));
暗黙の関数テンプレート化(IFTI)
関数テンプレートは、 関数引数の型から TemplateArgumentListが推論可能な場合、暗黙的にインスタンス化される。
T square(T)(T t) { return t * t; } writefln("The square of %s is %s", 3, square(3)); // Tはintであると推論される
型パラメータの推論は関数引数の順序に影響されない 。
TemplateArgumentListで指定された引数の数が TemplateParameterListのパラメータの数よりも少ない場合、引数は 左から右の順にパラメータを埋め、残りのパラメータは関数の引数から推論される 。
制限事項
暗黙的に推論される関数テンプレート型パラメータは、 少なくとも1つの関数パラメータの型に表示されなければならない。
void foo(T : U*, U)(U t) {} void main() { int x; foo!(int*)(x); // OK、Uは推論され、Tは明示的に指定される //foo(x); // エラー、推論できるのはUだけで、Tは推論できない }
テンプレートパラメータが推論される必要がある場合、 同名のメンバは、 static if なぜなら、推論はメンバーの使用方法に依存しているからだ。
template foo(T) { static if (is(T)) // Tはまだわからない... void foo(T t) {} // Tはメンバの使用法から推測される } void main() { //foo(0); // エラー: 引数の型から関数を推論できない foo!int(0); // 推論の必要がないのでOK }
パラメータの型がエイリアステンプレートインスタンスの場合、IFTIは動作しない。
struct S(T) {} alias A(T) = S!T; void f(T)(A!T) {} void main() { A!int v; //f(v); // エラー f!int(v); // OK }
型変換
テンプレート型パラメータが関数引数のリテラル式と一致する場合、 推論された型はそれらの狭義変換を考慮する可能性がある。
void foo(T)(T v) { pragma(msg, "in foo, T = ", T); } void bar(T)(T v, T[] a) { pragma(msg, "in bar, T = ", T); } void main() { foo(1); // 整数リテラル型はデフォルトでint型として解析される // であれば、Tはintと推論される short[] arr; bar(1, arr); // arrはshort[]であり、整数リテラル1は暗黙のうちに // shortに変換される。 // であれば、Tはshortと推論される。 bar(1, [2.0, 3.0]); // 配列リテラルはdouble[]として解析され、 // 整数リテラル1は暗黙のうちにdoubleに変換される。 // であれば、Tはdoubleと推論される。 }
動的配列およびポインタ引数の推論型パラメータは、 修飾されていない先頭を持つ。
void foo(T)(T arg) { pragma(msg, T); } void test() { int[] marr; const(int[]) carr; immutable(int[]) iarr; foo(marr); // T == int[] foo(carr); // T == const(int)[] foo(iarr); // T == immutable(int)[] int* mptr; const(int*) cptr; immutable(int*) iptr; foo(mptr); // T == int* foo(cptr); // T == const(int)* foo(iptr); // T == immutable(int)* }
戻り値の型推論
関数テンプレートは、通常の関数と同様に、関数内の ReturnStatementsに基づいて戻り値の型を推論することができる。 "auto関数"を参照。
auto square(T)(T t) { return t * t; } auto i = square(2); static assert(is(typeof(i) == int));
自動参照パラメータ
テンプレート化された関数には自動参照パラメータが含まれることがある。 自動参照パラメータは、対応する引数が l値である場合は参照パラメータとなり、 そうでない場合は値パラメータとなる。
int countRefs(Args...)(auto ref Args args) { int result; foreach (i, _; args) { if (__traits(isRef, args[i])) result++; } return result; } void main() { int y; assert(countRefs(3, 4) == 0); assert(countRefs(3, y, 4) == 1); assert(countRefs(y, 6, y) == 2); }
自動参照パラメータは自動参照戻り値属性と組み合わせることができる。
auto ref min(T, U)(auto ref T lhs, auto ref U rhs) { return lhs > rhs ? rhs : lhs; } void main() { int i; i = min(4, 3); assert(i == 3); int x = 7, y = 8; i = min(x, y); assert(i == 7); // 結果はl値である min(x, y) = 10; // xを10に設定する assert(x == 10 && y == 8); static assert(!__traits(compiles, min(3, y) = 10)); static assert(!__traits(compiles, min(y, 3) = 10)); }
デフォルト引数
暗黙のうちに推論されないテンプレート引数には、デフォルト値を設定できる。
void foo(T, U=T*)(T t) { U p; ... } int x; foo(x); // Tはint、Uはint*である
可変長引数関数テンプレートは、デフォルト値を持つパラメータを持つことができる。 これらのパラメータは、IFTIの場合、常にデフォルト値に設定される。
size_t fun(T...)(T t, string file = __FILE__) { import std.stdio; writeln(file, " ", t); return T.length; } assert(fun(1, "foo") == 2); // IFTIを使う assert(fun!int(1, "filename") == 1); // IFTIを使わない
テンプレートコンストラクタ
ConstructorTemplate: this TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt FunctionBody
テンプレートは、クラスおよび構造体のコンストラクタを生成するために使用できる。
列挙型と変数テンプレート
集約や関数と同様に、 変数宣言やマニフェスト定数にもテンプレートパラメータを指定でき、 初期化子がある場合、
enum bool within(alias v, T) = v <= T.max && v >= T.min; ubyte[T.sizeof] storage(T) = 0; const triplet(alias v) = [v, v+1, v+2]; static assert(within!(-128F, byte)); static assert(storage!(int[2]).length == 8); static assert(triplet!3 == [3, 4, 5]);
これらの宣言は、以下のテンプレート宣言に変換される。
template within(alias v, T) { enum bool within = v <= T.max && v >= T.min; } template storage(T) { ubyte[T.sizeof] storage = 0; } template triplet(alias v) { const triplet = [v, v+1, v+2]; }
エイリアステンプレート
AliasDeclarationにはオプションのテンプレートパラメータを指定することもできる。
alias ElementType(T : T[]) = T; alias Sequence(TL...) = TL;次のように短縮される。
template ElementType(T : T[]) { alias ElementType = T; } template Sequence(TL...) { alias Sequence = TL; }
ネストされたテンプレート
テンプレートが集合スコープまたは関数ローカルスコープで宣言されている場合、 インスタンス化された関数は暗黙的にその 外側のスコープのコンテキストを捕捉する。
class C { int num; this(int n) { num = n; } template Foo() { // 'foo'はクラスCオブジェクトの'this'参照にアクセスできる。 void foo(int n) { this.num = n; } } } void main() { auto c = new C(1); assert(c.num == 1); c.Foo!().foo(5); assert(c.num == 5); template Bar() { // 'bar'は'main'関数のローカル変数にアクセスできる。 void bar(int n) { c.num = n; } } Bar!().bar(10); assert(c.num == 10); }
上記の例では、Foo!().foo はfinal クラスのメンバ関数としてC と同じように動作し、Bar!().bar は 関数main() 内のネストされた関数として同じように動作する 。
集約型の制限事項
ネストされたテンプレートでは、非静的フィールドを集合型に追加することはできない。 ネストされたテンプレートで宣言されたフィールドは、暗黙的にstatic 。
ネストされたテンプレートでは、クラスまたはインターフェイスに仮想関数を追加することはできない。 ネストされたテンプレート内のメソッドは暗黙的にfinal となる。
class Foo { template TBar(T) { T xx; // 静的フィールドになる void func(T) {} // 暗黙的にfinalになる //abstract void baz(); // エラー、final関数は抽象化できない static T yy; // OK static void func(T t, int y) {} // OK } } void main() { alias bar = Foo.TBar!int; bar.xx++; //bar.func(1); // エラー、thisがない auto o = new Foo; o.TBar!int.func(1); // OK }
暗黙的なネスト
テンプレートにテンプレートエイリアスパラメータがあり、 ローカルシンボルでインスタンス化されている場合、インスタンス化された関数は 与えられたローカルシンボルの実行時データにアクセスするために暗黙的にネストされる 。
template Foo(alias sym) { void foo() { sym = 10; } } class C { int num; this(int n) { num = n; } void main() { assert(this.num == 1); alias fooX = Foo!(C.num).foo; // fooXは暗黙的にメンバ関数になるので、&fooXは // デリゲートオブジェクトを返す。 static assert(is(typeof(&fooX) == delegate)); fooX(); // 有効な'this'参照を使って呼び出される。 assert(this.num == 10); // OK } } void main() { new C(1).main(); int num; alias fooX = Foo!num.foo; // fooXは暗黙のうちに入れ子関数になるので、 // &fooXはデリゲートオブジェクトを返す。 static assert(is(typeof(&fooX) == delegate)); fooX(); assert(num == 10); // OK }
関数だけでなく、インスタンス化されたクラスや構造体型も、 暗黙的に捕捉されたコンテキストを介してネスト化される可能性がある。
class C { int num; this(int n) { num = n; } class N(T) { // インスタンス化されたクラスN!Tは、Cで入れ子になることができる T foo() { return num * 2; } } } void main() { auto c = new C(10); auto n = c.new N!int(); assert(n.foo() == 20); }
void main() { int num = 10; struct S(T) { // インスタンス化された構造体S!Tは、main()のネストになる T foo() { return num * 2; } } S!int s; assert(s.foo() == 20); }
テンプレート化されたstruct は、 エイリアスされた引数として渡されたローカルシンボルでインスタンス化されると、ネストされたstruct になることができる。
struct A(alias F) { int fun(int i) { return F(i); } } A!F makeA(alias F)() { return A!F(); } void main() { int x = 40; int fun(int i) { return x + i; } A!fun a = makeA!fun(); assert(a.fun(2) == 42); }
コンテキストの制限
現在、ネストされたテンプレートは、最大で1つのコンテキストを捕捉できる。典型的な 例として、テンプレート化されていないメンバ関数は、 テンプレートエイリアスパラメータを使用してローカルシンボルを取得できない。
class C { int num; void foo(alias sym)() { num = sym * 2; } } void main() { auto c = new C(); int var = 10; c.foo!var(); // NG、foo!varは'this'と'main()'の2つのコンテキストを必要とする }
しかし、あるコンテキストが他のコンテキストから間接的にアクセス可能である場合は、許可される。
int sum(alias x, alias y)() { return x + y; } void main() { int a = 10; void nested() { int b = 20; assert(sum!(a, b)() == 30); } nested(); }
2つのローカル変数a とb は異なるコンテキストにあるが、 外側のコンテキストは間接的に内側のコンテキストからアクセスできるため、ネストされた テンプレートインスタンスsum!(a, b) は内側のコンテキストのみを捕捉する 。
再帰的テンプレート
テンプレート機能は、いくつかの興味深い効果を生み出すために組み合わせることができる。 例えば、複雑な関数のコンパイル時の評価などである。 例えば、階乗テンプレートは以下のように記述できる。
template factorial(int n) { static if (n == 1) enum factorial = 1; else enum factorial = n * factorial!(n - 1); } static assert(factorial!(4) == 24);
CTFE(コンパイル時関数実行)による 階乗の代替方法の詳細については、 「テンプレートの再帰」を参照のこと。
テンプレート制約
Constraint: if ( Expression )
制約は、 テンプレートパラメータリストで可能な範囲を超えて、 テンプレートに一致する引数に追加の制約を課すために使用される。 式はコンパイル時に計算され、 ブール値に変換される結果を返す。 その値が true の場合、テンプレートは一致し、 そうでない場合は一致しない。
例えば、以下の関数テンプレートはN の奇数値のみと一致する。
void foo(int N)() if (N & 1) { ... } ... foo!(3)(); // OK、マッチする foo!(4)(); // エラー、マッチしない
テンプレート制約は、集約型(構造体、クラス、共用体)でも使用できる。 制約は、ライブラリモジュールと効果的に使用できる std.traits。
import std.traits; struct Bar(T) if (isIntegral!T) { ... } ... auto x = Bar!int; // OK、intはintegral型である auto y = Bar!double; // エラー、doubleは制約を満たさない
DEEPL APIにより翻訳、ところどころ修正。
このページの最新版(英語)
このページの原文(英語)
翻訳時のdmdのバージョン: 2.109.1
ドキュメントのdmdのバージョン: 2.109.1
翻訳日付 :
HTML生成日時:
編集者: dokutoku