テンプレート・ミックスイン
TemplateMixin は、 任意の種類の宣言をテンプレートから取り出し、 現在のスコープへ導入します。TemplateMixinDeclaration: template TemplateIdentifier ( TemplateParameterList ) { DeclDefs } TemplateMixin: mixin TemplateIdentifier ; mixin TemplateIdentifier MixinIdentifier ; mixin TemplateIdentifier !( TemplateArgumentList ) ; mixin TemplateIdentifier !( TemplateArgumentList ) MixinIdentifier ; MixinIdentifier: Identifier
TemplateMixin は、モジュール、クラス、構造体、共用体の宣言リストの途中や、 あるいは一つの文としての位置に書くことができます。 TemplateIdentifier はテンプレートの宣言を参照します。 もしテンプレートが引数を取らないのであれば、 !(TemplateArgumentList) を省略した形式で書いても構いません。
テンプレートのインスタンス化とは異なり、テンプレートのmixinの本体は、 テンプレート定義のあるスコープではなく、mixinされた先のスコープの中で評価されます。 これは、テンプレートの本体をカット&ペーストして mixin したい場所に貼り付けるのと似た動作と言えるでしょう。 この機能は、パラメタ抽象された'鋳型'コードを挿入するのにはもちろん、 ただのインスタンス化では不可能な、 テンプレートネスト関数の作成にも役立ちます。
template Foo() { int x = 5; } mixin Foo; struct Bar { mixin Foo; } void test() { writefln("x = %d", x); // 5 を表示 { Bar b; int x = 3; writefln("b.x = %d", b.x); // 5 を表示 writefln("x = %d", x); // 3 を表示 { mixin Foo; writefln("x = %d", x); // 5 を表示 x = 4; writefln("x = %d", x); // 4 を表示 } writefln("x = %d", x); // 3 を表示 } writefln("x = %d", x); // 5 を表示 }Mixinはパラメタ化することができます:
template Foo(T) { T x = 5; } mixin Foo!(int); // int型変数xを作成クラスに仮想関数を付け加えることも可能です:
template Foo() { void func() { writefln("Foo.func()"); } } class Bar { mixin Foo; } class Code : Bar { void func() { writefln("Code.func()"); } } void test() { Bar b = new Bar(); b.func(); // Foo.func() を呼び出す b = new Code(); b.func(); // Code.func() を呼び出す }Mixin は、テンプレート宣言のあるスコープではなく、 mixin が現れたスコープにおいて評価されます:
int y = 3; template Foo() { int abc() { return y; } } void test() { int y = 8; mixin Foo; // ローカルの y が選ばれる。グローバルの y ではなく。 assert(abc() == 8); }alias引数を使うと、シンボルをパラメタに取ることができます:
template Foo(alias b) { int abc() { return b; } } void test() { int y = 8; mixin Foo!(y); assert(abc() == 8); }以下の例は、任意の文に対して使える汎用の"ダフのデバイス"を実装するのに Mixin を使っています。(この例の場合、任意の文にできる箇所を太字で表記しています。) ネストした関数もdelegateリテラルで同様に生成することができますし、 これらはコンパイラによるインライン化が見込めます:
template duffs_device(alias id1, alias id2, alias s) { void duff_loop() { if (id1 < id2) { typeof(id1) n = (id2 - id1 + 7) / 8; switch ((id2 - id1) % 8) { case 0: do { s(); case 7: s(); case 6: s(); case 5: s(); case 4: s(); case 3: s(); case 2: s(); case 1: s(); } while (--n > 0); } } } } void foo() { writefln("foo"); } void test() { int i = 1; int j = 11; mixin duffs_device!(i, j, delegate { foo(); } ); duff_loop(); // foo() を10回実行 }
ミックスインのスコープ
Mixinの中の宣言は、周囲のスコープに 'import' されます。 Mixinの中の宣言と同じ名前の宣言が周囲のスコープに存在した場合、 周囲のスコープの宣言が、 Mixinのそれを上書きします:int x = 3; template Foo() { int x = 5; int y = 5; } mixin Foo; int y = 3; void test() { writefln("x = %d", x); // 3を表示 writefln("y = %d", y); // 3を表示 }二つの異なるMixinが同じスコープへ置かれて、 しかもおのおのが同名の宣言を行っている場合、 その宣言を参照すると曖昧さのためエラーになります:
template Foo() { int x = 5; void func(int x) { } } template Bar() { int x = 4; void func() { } } mixin Foo; mixin Bar; void test() { writefln("x = %d", x); // エラー、xは曖昧 func(); // エラー、func は曖昧 }
この func() の呼び出しが曖昧とされるのは、 Foo.func と Bar.func が異なるスコープにあるからです。
mixin宣言に MixinIdentifier が指定されていれば、 それを使って曖昧さを解消できます:
int x = 6; template Foo() { int x = 5; int y = 7; void func() { } } template Bar() { int x = 4; void func() { } } mixin Foo F; mixin Bar B; void test() { writefln("y = %d", y); // 7 を表示 writefln("x = %d", x); // 6 を表示 writefln("F.x = %d", F.x); // 5 を表示 writefln("B.x = %d", B.x); // 4 を表示 F.func(); // Foo.func を呼び出す B.func(); // Bar.func を呼び出す }
alias宣言を使って、 違うmixinで宣言された関数をオーバーロードすることは可能です:
template Foo() { void func(int x) { } } template Bar() { void func() { } } mixin Foo!() F; mixin Bar!() B; alias F.func func; alias B.func func; void main() { func(); // B.func を呼び出す func(1); // F.func を呼び出す }
宣言は周囲のもので上書きされますが、 mixinの内部では、それ自身の独自のスコープを持ちます。
int x = 4; template Foo() { int x = 5; int bar() { return x; } } mixin Foo; void test() { writefln("x = %d", x); // 4 を表示 writefln("bar() = %d", bar()); // 5 を表示 }