3-c1. Template in D

初出: 2007/03/07
最新: 2009/01/30
[3. D言語各論] に戻る

目次

他の言語の近い機能(template, generics, module, ...)と似ている部分もあり違う部分もありの D のテンプレートの基本部分を、だいたいこんな感じですよーと整頓してみました。 これでテンプレート周りの全仕様を説明しきっているわけでは全然ありませんので、 もっと詳細な機能の説明に関しては公式のリファレンスをご覧ください。

最後におまけとして、C++ の Template をヘビーに使っている読者を前提とした 比較コーナーです。

テンプレートは定義をまとめて再利用可能にする

わりと適当な例ですが。 いろんなクラスに、名前を設定したり取得したりする機能を持たせたいとします。

class Abc
{
    private string name;
    public void   setName( string newName ) { name = newName; }
    public string getName() { return name; }

    // ... Abc Specific Things ...
}

class Def
{
    private string name;
    public void   setName( string newName ) { name = newName; }
    public string getName() { return name; }

    // ... Def Specific Things ...
}

class Ghi
{
    private string name;
    public void   setName( string newName ) { name = newName; }
    public string getName() { return name; }

    // ... Ghi Specific Things ...
}

同じコードを何回も書くのは無駄ですね。一カ所にまとめたいです。

// template !!
template nameImpl()
{
    private string name;
    public void   setName( string newName ) { name = newName; }
    public string getName() { return name; }
}

class Abc
{
    mixin nameImpl;
    // ... Abc Specific Things ...
}

class Def
{
    mixin nameImpl;
    // ... Def Specific Things ...
}

class Ghi
{
    mixin nameImpl;
    // ... Ghi Specific Things ...
}

こういう風に、関数や変数(あと、この例では出てきませんでしたがクラス)の定義を一カ所にまとめて 再利用できるようにするのが、D言語の template です。定義をまとめるときにはキーワード template を使って、それを実際の定義として展開するときには mixin と書きます。

テンプレートはパラメタ付きで定義を再利用可能にする

なぜか名前をstringではなくwstringで保持したいクラスも実装しなきゃいけなくなりました。

template nameImpl(StrT)
{
    private StrT name;
    public void setName( StrT newName ) { name = newName; }
    public StrT getName() { return name; }
}

class Abc
{
    mixin nameImpl!(string);
    // ... Abc Specific Things ...
}

class Xyz
{
    mixin nameImpl!(wstring);
    // ... Xyz Specific Things ...
}

あわてず騒がず、テンプレートの中の可変部分を、パラメタとして追い出します。 この場合は string や wstring に変わる部分を、StrT というパラメタにしました。 template 定義の時には、テンプレート名の後ろの括弧の中身パラメタ名を並べます。 mixin で使うときは、テンプレート名 ! ( 引数 ) の形になります。 "!" マークをお忘れ無く。

パラメタにできるのは、型、整数、浮動小数点数、文字列、識別子(後述)、タプル(後述)です。

テンプレートはmixinされた場所の定義を参照できる

toString メソッドの返値を標準出力に吐き出す dump というメソッドの定義を再利用したくなりました。

template debugPrintImpl()
{
    void dump() { derr.writeLine( toString() ); }
}

class MyObject
{
    string toString() { return "foo"; }
    mixin debugPrintImpl;
}

class Abc
{
    mixin nameImpl!(string);
    string toString() { return getName(); }
    mixin debugPrintImpl;
}

そのまま書けば大丈夫です。

ここまでに紹介した特徴の組み合わせで、 RubyのEnumerableモジュールみたいなの が実装できます。

mixin しないで直接使う

Enumerable を例にあげたように、D のテンプレートは Rubyのモジュール にかなり近いものがあると思います。ML のモジュールにも似ているかな。 つまり、include しないで、そのまま使うという使い方もありです。 特に、パラメタ付き定義の場合に威力を発揮します。

配列が特定の値を含んでいるかどうか検査する関数、というお題で考えてみましょう。

bool contains( int[] array, int value )
{
    foreach( e; array )
      if( value == e )
        return true;
    return false;
}

bool contains( real[] array, real value )
{
    foreach( e; array )
      if( value == e )
        return true;
    return false;
}

...

void main()
{
   int[] f = [1,2,3,5,8,13,21,34,55,89];
   if( contains(f,10) ) ...

   real[] c = [3.14, 2.71, 0.33, -42];
   if( contains(c, 3.14) ) ...
}

配列の型ごとにほとんど同じ contains 関数を実装するのは無駄です。テンプレートでまとめましょう。

template containsImpl(T)
{
    bool contains( T[] array, T value )
    {
        foreach( e; array )
          if( value == e )
            return true;
       return false;
    }
}

void main()
{
   int[] f = [1,2,3,5,8,13,21,34,55,89];
   if( containsImpl!(int).contains(f,10) ) ...

   real[] c = [3.14, 2.71, 0.33, -42];
   if( containsImpl!(real).contains(c, 3.14) ) ...
}

mixin しないで、「テンプレート名!(引数).テンプレートのメンバ」の形で中の 関数やクラスを使うこともできます。

関数テンプレート

ところで先ほどの例、

containsImpl!(int).contains(f,10)

って長すぎますよね。こういう使い方はよくあるので、短く書く方法がD言語側に用意されています。

template contains(T)
{
    bool contains( T[] array, T value )
    {
        foreach( e; array )
          if( value == e )
            return true;
       return false;
    }
}

contains!(int)(f,10)

テンプレートの名前と、中で1個だけ定義する名前を一致させると、 「テンプレート名!(引数)」だけでメンバを参照できるようになっています。 ええと、それでもまだ長いですね。実はこれ、

contains(f,10)

こう書くことができます。「int[]型の配列とintの10を渡してるんだから、引数Tは当然intでしょ」 と自動的に推論してくれます。

テンプレートの定義側もまだ長すぎです。テンプレート名と関数名で同じ名前を二回書くのは格好悪い。 これも略記があって、

bool contains(T)( T[] array, T value )
{
    foreach( e; array )
      if( value == e )
         return true;
    return false;
}

関数名のうしろにテンプレート引数を並べる形にすると、template キーワードも省略可能です。

このように

を通して、テンプレートを「いろんな型に適用できる関数」っぽく使う使い方は 「関数テンプレート」と呼ばれています。このスタイルは、 C++ の Template や Java の Generics に非常によく似ています。

クラステンプレート

「関数テンプレート」の定義と同じ略記が、クラスでも使えます。「クラステンプレート」です。

template stackImpl(T)
{
    class Stack { ... }
}

の代わりに

template Stack(T)
{
    class Stack { ... }
}

の代わりに

class Stack(T)
{
    private T[] data;
    T pop() { T e = data[$-1]; data=data[0..$-1]; return e; }
    T push(T e) { data ~= e; }
}

と定義すると、new Stack!(int) でint型のスタックが作れるし、new Stack!(real) で real 型のスタックが作れます。関数と違ってクラスでは実際の引数から推論みたいなことができないので、 !(...) は省略できません。(「コンストラクタの引数から推論できるときはするというのはどう?」 という議論が時々Dのnewsgroupであがるので、ひょっとしたら採用されるかもしれませんが)

これも C++ の Template、Java, C# の Generics のスタイルによく似た使い方ですね。

Duck Typing みたいなもの

Matzにっきで話題になっていたので、 C++のテンプレートでダックタイピング の例をDに直訳してみました。「quack というメソッドを持ってるオブジェクトならなんでも受け入れるよ」 というfunc関数を、関数テンプレートとして実装します。

import std.stdio;

class Duck {
    void quack() { writeln("gaaa"); }
}

class Foo {
    void quack() { writeln("...."); }
}

class Programmer {
    void quickhack() {}
}

void func(Ducky)( Ducky ducky ) {
    ducky.quack();
}

void main() {
    func( new Duck );
    func( new Foo );
    // func( new Programmer ); ←これはコンパイルエラー
}

Dのテンプレートは、仕組み的には C++ のそれと全く同じです。 引数の違う実体は全部別のインスタンスとして、コンパイル時にマクロ的に展開されます。 型チェックも、JavaやC#のジェネリクスのようなテンプレートの段階での型チェックは入らなくて、 実際にmixinや関数適用の形で使った時点で初めて型チェックが行われます。

テンプレートと識別子パラメタ

ここまでの例は全て「型」をパラメタとして受け取るテンプレートでしたが、 他のものでもパラメタ化できます。

引数を alias とすると、変数やモジュール、テンプレートをパラメタにとるようになります。 「他のテンプレートを引数にとるテンプレート」つまりC++でいうところのテンプレートテンプレート引数 としての使い方が多分一番多いです。

class LinkedList(T) { ... }
class Vector(T) { ... }

class Stack(T, alias List)
{
    List!(T) data;
    ...
}

Stack!(int, LinkedList) s; // 実装にLinkedListを使ったStack
Stack!(real, Vector) t; // 実装にVectorを使ったStack

テンプレートと文字列パラメタと文字列mixin

「型」「識別子」の他に、整数や浮動小数点数なんかもパラメタにできます。 C++のTemplateでいう「値パラメタ」と同じように使えます。 整数で次元を指定して使えるベクトルや行列クラスなどなど。

「文字列」も値パラメタとして取ることができるのですが、これは特に面白い使い方ができます。 こんな例を考えてみましょう。

import std.stdio;

class Point
{
    private int _x;
    public int x() { return _x; }

    private int _y;
    public int y() { return _y; }

    this(int ix, int iy) { _x=ix; _y=iy; }
}

void main()
{
    Point pt = new Point(10, 20);
    writefln( "(%d, %d)", pt.x, pt.y );
}

Dのゼロ引数関数は括弧なしで呼び出せるので、こんな風にプロパティ代わりに使われます。 さて、ここで、x と y の定義は明らかに同じパターンの繰り返しです。 他にも使い回せそうなパターンです。テンプレートを使って処理を共通化してみましょう。 要は Rubyのattr_reader です。

template attr_reader(T, string name)
{
    mixin( "private T _" ~ name ~ ";" );
    mixin( " public T " ~ name ~ "(){ return _" ~ name ~ ";}" );
}

class Point
{
    mixin attr_reader!(int, "x");
    mixin attr_reader!(int, "y");

    this(int ix, int iy) { _x=ix; _y=iy; }
}

attr_reader テンプレートは、型と、プロパティの名前を文字列で受け取ります。 注目すべきは attr_reader の中身で、mixin にテンプレートではなく文字列を 渡しています。mixin は、文字列をD言語のコードとして解釈してその場に変数やメソッドの定義として 展開します。(式が来る位置でmixin(文字列)すると式として展開、文が来る位置なら文として展開されます。)

テンプレートとタプルパラメタ

パラメタにできるものシリーズ最終回は「タプル」です。

template Foo(T...) {}

mixin Foo!();
mixin Foo!(int);
mixin Foo!(real, float, double);

まあようは可変長引数のテンプレート版なのですが、こういう風に、可変個の引数を受け取れます。 これは関数テンプレートと組み合わせると、引数の型も数もなんでも受け入れる関数が作れます。

void print_all(T...)(T xs)
{
    foreach(x; xs)
        writeln( x, " : ", typeid(typeof(x)) );
}

void main()
{
    print_all( 10, "abc", 3.14 );
    // 10 : int
    // abc : char[3]
    // 3.14 : double
}

関数の実際の引数のところに型のようにタプルを使うと(T xs)、対応する xs が、「仮引数のタプル」 となります。タプルは配列のようにforeachで回ったり、添え字やスライスでアクセスすることができます。

more...

この「タプル」ですが、関数型言語や、Pythonその他いくつかの言語にある「タプル」とは ちょっと意味が違うので注意が必要です。

Dの「タプル」は、型でもないし、実行時の値でもありません。コンパイル時にだけ現れる コンパイラ内のデータ構造です。

template hoge(T...)
{
    void foo(int x, int y, T zs) {}
}

mixin hoge!(int,int); // foo(int,int,int,int)を定義

void bar(T...)(T xs)
{
    foo( 1, xs[0..2], 4 );
}

void main()
{
    bar( 2, 3 );     // ok foo(1,2,3,4)
    //bar( 2 );      // err
    bar( 10,20,30 ); // ok foo(1,10,20,4)
}

関数の実引数リスト/仮引数リストのところにタプルを使うと、 例えば foo は int と int とタプルを受け取る関数、ではなくて、int と int と int と int を 受け取る関数になります。

C++ Template Metaprogrammer のための D Template

メタプログラミングするという観点からみて、D の Template はどうだろうか?と比較してみます。 「これができる」「これができない」「でもこういう回避策がある」みたいな感じでどんどこ並べて 行きます。

こまごま

typeof

typeof があります。D言語にはconst/volatile修飾子や参照型はないので、 特に複雑なこともなく typeof があります。

alias template

あります。C++0x でいう typedef template です。 テンプレート名と中で定義する名前を一致させれば省略可 のルールを導入したときのWalterはここまで考えていたのか、とにかく、 このルールを使うと次のようなテンプレートが書けます。

template array_of(T) { alias T[] array_of; }

array_of!(int) x; // int[] x

特殊化

explicit。コロンの後ろに具体的な型を書いて特殊化します。

class Vector(T) {}
class Vector(T: bool) {}

partial。コロンの後ろに、型変数を使って型を書いて特殊化します。

class Vector(T) {}
class Vector(T: T*) {} // ポインタ限定
class Vector(T: List!(T)) {} // List限定

特殊化があるということは、C++テンプレートメタプログラミングの基礎の基礎的な技術は だいたい再現できるということになります。あと、特殊化の際に SFINAE もあるので、 その辺のテクニックも行けます。この項ではその辺全部ご存じのものとしてすっ飛ばします。 まあ要するに基本は同じです。

型変数を2個以上使った部分特殊化。

class Vector(T: T[S],S) {} // 連想配列限定

2個目以降の型変数を後ろに書くのが微妙に気持ち悪いというか、引数が複数のテンプレートの特殊化と文法的に ごちゃごちゃになっているのですが。この例のように変数宣言(S)より左にパターン(T[S])が出てきたら 複数変数特殊化として扱われるようです。ただし

class Vector(T) {}
class Vector(T: T[S],S) {}

Vector!(int[char]) x;

これが曖昧でエラーとなってしまうので、あるテンプレートの「特殊化」というよりは、 「連想配列のみで使えるテンプレート」みたいな限定版を書く用途を意図しているのかもしれません。 将来のバージョンでいつの間にか直ってるかもしれません。

なお、部分特殊化のこのSyntaxさえ諦めれば、意味的には同じ効果を実現する技が発見されています。 いくつか前提知識がいるので、その方法についてはもうちょい下の方で。

名前の探索

ADL

Argument Dependent Lookup。ありません。関数は、その時点でimportされているモジュール からのみ探索されます。引数の名前空間は見に行きません。演算子オーバーロードは 全てメンバ関数なので、ADLは要らないというスタンスみたいです。

オーバーロード

同じモジュール内では、同名の関数を多重定義することが可能で、 引数の型の適合度に応じててもっとも適切な関数が呼び出されます。 違うモジュールの関数どうしはオーバーロード集合という概念に基づいて名前解決が行われます (複数のモジュールにマッチする関数が合った場合は適合度による選別は行われず、単純にエラーになります。) また、ローカルの関数があれば優先されます。

module foo;
void f() {}
void g() {}
module bar;
void f() {}
void g() {}
module buz;
import foo, bar;
void g() {}

void main()
{
    f(); // エラー。foo.f と bar.f が conflict
    g(); // buz.g が呼ばれる
}

それはまあいいのですが

template foo(T)
{
    void doNothing(T x) {}
}

mixin foo!(int);
mixin foo!(char);

void main()
{
    doNothing(100); // エラー。doNothing がconflictしてる
}

mixin で追加した関数同士もオーバーロードできないのが微妙に不便です。 しかしこれは実は

alias foo!(int).doNothing  doNothing;
alias foo!(char).doNothing doNothing;

alias で関数を持ち込むとオーバーロードできるという回避策があります。 親クラスのメンバを隠さないように using declaration で明示的に名前を持ち込むようなノリです。

static if

C++と同じように、特殊化を使って条件分岐を実装することもできます。 が、もっと直接的に、static if という構文も用意されています。

template fib(int n)
{
    static if( n <= 1 )
        const fib = 1;
    else
        const fib = fib!(n-2) + fib!(n-1);
}

こういうもの。static if の中で型の一致を判定するための、is式 というのもついでに用意されています。 is式では、テンプレートの部分特殊化よりちょっと高度な特殊化もできます。

class Vector(T)
{
    static if( is(T == bool) )
    {
        // boolの場合の実装
    }
    else static if( is(T U == U*) )
    {
        // ポインタの場合の実装
    }
    else
    {
        // その他
    }
}

この例なら普通の特殊化の方がベターですが、 static ifを使った方が自然に書ける場面も結構多いので、重宝します。

is式の重要な機能として、複合型の構成要素を取ってくる特殊化があります。

void f(int, char, real) {}

template foo(T)
{
    static if( is(T PS == function) ) {
        // PS は タプル(int,char,real)
    }
    static if( is(T R == return) ) {
        // R は void
    }
}

void main()
{
    int x = foo!(typeof(f)).t;
}

なんか変わった文法ですが。上のstatic ifであれば、Tが関数型だったらis式がtrueになって、 同時にPSが関数引数型のタプルになります。この、引数タプルをとってくる機能とタプル引数を 使うと、「プリプロセッサで上限まで展開」とかやらなくても任意個の引数の関数を統一的に 扱えてかなり便利です。

部分特殊化

いよいよ上で予告した、部分特殊化を完全にやる方法について。 私は shinhさんkurimuraさん のコードを見てこの方法を知りました。

Dでは、テンプレート部分特殊化やis式の判定よりも、関数テンプレートの引数の型推論の方が強力です。 部分特殊化で連想配列とそれ以外を分けることはできなくても、連想配列だけを受け取る関数テンプレートは書けます。

void is_aa(V,K)( V[K] x );

返値の型で、細かい情報も返せます。

class TypePair(K,V) {
  alias K key;
  alias V val;
}
TypePair!(K,V) is_aa(V,K)( V[K] x );

typeof があるので、返値の型は型として再びメタプログラミングに使えます。

template foo(T)
{
    alias typeof(is_aa(T.init)) tp;
    alias tp.key KeyOfT;
    alias tp.val ValOfT;
}

そして、static if & is式が分岐を実現します。

template foo(T)
{
    static if( is(typeof(is_aa(T.init)) tp) ) is式は、typeofの中身でSubstitution Failureが起こってなければtrue
    {
        // 連想配列ならこっち
    }
    else
    {
        // 違うときはこっち
    }
}

できた!同じように、is_aaの代わりに、例えば「Vector!(T) のみを引数にとる関数テンプレート」を使えば、 Vectorのみにマッチする特化版テンプレートができあがります。

コンパイル時関数実行

フィボナッチ関数計算するのにテンプレートで書くのって面倒だよね、ということで。 Dでは、普通に書いた関数にコンパイル時定数を喰わせると、 ハイパワー版定数たたみ込みとして、コンパイル時に実行してくれることになっています。

int fib( int x )
{
    return x<=1 ? 1 : fib(x-1)+fib(x-2);
}

class foo(int N) {}

void main()
{
    foo!(fib(10)) x; // fib(10) はコンパイル時定数
}

といっても、フィボナッチを普通の記法で書けても実際問題としては特にうれしくもないです。 これは 文字列mixin との組み合わせを目的としているみたいです。 つまり、「文字列処理する関数を普通のD言語で書く」「コンパイル時にはガンガン文字列処理」 「最後にmixinでコード生成」というスタイルのメタプログラミング。

テンプレート制約

Boost.Enable_If 相当の機能として、 テンプレート宣言を if で修飾して条件をかけることができます。

void foo(T)( T x ) if( is( T == int ) )
{
    ...
}

特殊化よりも複雑な条件で制約を書くことができ、SFINAEのようなトリックに頼る必要もなく、 static if による分岐と違って、テンプレートの暗黙のインスタンス化やオーバーロード解決を犠牲にせずに済みます。

[3. D言語各論] に戻る

presented by k.inaba   under NYSDL.