D 1.0   D 2.0
About Japanese Translation

Last update Wed Nov 10 12:48:19 2010

テンプレート

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);

インスタンス化のスコープ

TemplateInstantanceTemplateDeclaration の宣言されたスコープに あるものとして常に扱われます。テンプレート仮引数は、 パラメタから推論された型への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 のスコープ内で評価されます。

引数推論

テンプレートパラメタの型は、 インスタンス化時に、 対応する引数と比較することで推論されます。

各々の型パラメータについて、全てのパラメータの型が推論されるまで 以下の規則が適用されます:

  1. そのパラメタに対する特殊化版が存在しなければ、 パラメタ型はテンプレート引数そのものに設定されます。
  2. 型パラメタに依存した特殊化版があれば、 そのパラメタ内の型は、 テンプレート引数型の対応する部分へと設定されます。
  3. 全ての型引数を調べた後、 まだ型の決定されていないパラメタがあれば、 そのパラメタは TemplateArgumentList 中で同じ位置にある 引数型へと設定されます。
  4. 以上のルールで一意に定まらないパラメタがあれば、 エラーです。

例えば:

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名、テンプレート名、テンプレートインスタンスの名前です。

タプル引数

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)(); }   // エラー