テンプレート

テンプレートは、コードをパターンとして記述し、コンパイラが自動的にプログラムコードを生成できるようにする機能だ。ソースコードの一部は、プログラム内で実際に使用されるまでコンパイラに埋め込ませるようにすることができる。

テンプレートは、特定の型に縛られることなく、汎用的なアルゴリズムやデータ構造を記述できることから、ライブラリなどで特に有用だ。

他の言語のテンプレートサポートと比較すると、Dのテンプレートは非常に強力で広範だ。この章では、テンプレートの詳細については説明しない。関数、構造体、およびクラスのテンプレート、および型テンプレートパラメータについてのみ説明する。テンプレートの詳細については、テンプレートの詳細の章で説明する。Dテンプレートの完全なリファレンスについては、Philippe Sigaud 著のD Templates: A Tutorialを参照。

テンプレートの利点を見るために、括弧内の値を出力する関数から始めよう。

void printInParens(int value) {
    writefln("(%s)", value);
}
D

パラメータはintとして指定されているため、この関数はint型の値、またはintに自動的に変換できる値でのみ使用できる。例えば、コンパイラは浮動小数点型でこの関数を呼び出すことを許可しない。

プログラムの要件が変更され、他の型も括弧で囲んで出力する必要が生じたとしよう。この問題の解決策のひとつは、関数のオーバーロードを利用して、その関数が使用されるすべての型に対してオーバーロードを用意することだ。

// すでに存在する関数
void printInParens(int value) {
    writefln("(%s)", value);
}

// 'double'に対する関数のオーバーロード
void printInParens(double value) {
    writefln("(%s)", value);
}
D

この解決策は、realやユーザー定義の型などでは関数を使用できなくなるため、拡張性に欠ける。他の型に対して関数をオーバーロードすることは可能だが、そのコストは膨大になる可能性がある。

ここで重要な点は、パラメータの型に関係なく、オーバーロードの内容はすべて、単一のwritefln()という同じものになることだ。

このような汎用性は、アルゴリズムやデータ構造ではよく見られる。例えば、二分探索アルゴリズムは、要素の型に依存しない。このアルゴリズムは、検索の具体的な手順と操作に関するものだからだ。同様に、リンクリストデータ構造も、要素の型に依存しない。リンクリストは、要素の型に関係なく、要素がコンテナにどのように格納されるかに関するものだからだ。

テンプレートはこのような状況で有用だ:コードがテンプレートとして記述されると、コンパイラはプログラム内のそのコードの実際の使用方法に応じて、同じコードのオーバーロードを自動的に生成する。

前述のように、この章では、関数、構造体、およびクラスのテンプレート、および型テンプレートパラメータについてのみ説明する。

関数テンプレート

関数をテンプレートとして定義すると、その関数で使用される1つ以上の型が未指定のままになり、後でコンパイラによって推論される。

指定されていない型は、関数名と関数パラメータリストの間に位置するテンプレートパラメータリスト内で定義される。そのため、関数テンプレートには2つのパラメータリスト、つまりテンプレートパラメータリストと関数パラメータリストがある。

void printInParens(T)(T value) {
    writefln("(%s)", value);
}
D

上記のテンプレートパラメータリスト内のTは、Tが任意の型であることができることを意味する。Tは任意の名前だが、"型"の頭文字をとったもので、テンプレートではよく使われる。

Tは任意の型を表すため、上記のprintInParens()のテンプレート定義は、ユーザー定義のものを含め、ほぼすべての型で使用するのに十分だ。

import std.stdio;

void printInParens(T)(T value) {
    writefln("(%s)", value);
}

void main() {
    printInParens(42);           // intを使用
    printInParens(1.2);          // doubleを使用

    auto myValue = MyStruct();
    printInParens(myValue);      // MyStructを使用
}

struct MyStruct {
    string toString() const {
        return "hello";
    }
}
D
templates.1

コンパイラは、プログラム内のprintInParens()のすべての使用を考慮し、それらの使用をすべてサポートするコードを生成する。その後、この関数は、intdouble、およびMyStructに対して明示的にオーバーロードされたかのようにコンパイルされる。

/* Note: These functions are not part of the source
 *       code. They are the equivalents of the functions that
 *       the compiler would automatically generate. */

void printInParens(int value) {
    writefln("(%s)", value);
}

void printInParens(double value) {
    writefln("(%s)", value);
}

void printInParens(MyStruct value) {
    writefln("(%s)", value);
}

プログラムの出力は、関数テンプレートのこれらの異なるインスタンス化によって生成される。

(42)
(1.2)
(こんにちは)

各テンプレートパラメータは、複数の関数パラメータを決定することができる。例えば、次の関数テンプレートの2つの関数パラメータと戻り値の型は、その単一のテンプレートパラメータによって決定される。

/* 'value'と等しい要素を除く'slice'の
 * コピーを返す。 */
T[] removed(T)(const(T)[] slice, T value) {
    T[] result;

    foreach (element; slice) {
        if (element != value) {
            result ~= element;
        }
    }

    return result;
}
D
複数のテンプレートパラメータ

関数テンプレートを変更して、括弧も受け取るようにしよう。

void printInParens(T)(T value, char opening, char closing) {
    writeln(opening, value, closing);
}
D

これで、異なる括弧のセットで同じ関数を呼び出すことができるようになった。

printInParens(42, '<', '>');
D

括弧を指定できることで関数の使いやすさは向上するが、括弧の型をcharと指定すると、wchardcharの型の文字で関数を呼び出せなくなるため、柔軟性が低下する。

printInParens(42, '→', '←');      // ← コンパイルエラー
D
エラー: テンプレートdeneme.printInParens(T)は、
引数の型からテンプレート関数を推測できない !()(int,wchar,wchar)
Undefined

1つの解決策は、括弧の型をdcharと指定することだが、この場合、stringやユーザー定義の型で関数を呼び出すことができなくなるため、まだ不十分だ。

別の解決策は、括弧の型もコンパイラに任せることだ。特定のcharの代わりに、追加のテンプレートパラメータを定義すれば十分だ。

void printInParens(T, ParensType)(T value,
                                  ParensType opening,
                                  ParensType closing) {
    writeln(opening, value, closing);
}
D

新しいテンプレートパラメータの意味は、Tの意味と同様だ。ParensTypeは、任意の型にすることができる。

これで、さまざまな種類の括弧を使用することが可能になった。以下は、wcharおよびstringを使用した例だ。

printInParens(42, '→', '←');
printInParens(1.2, "-=", "=-");
D
→42←
-=1.2=-

printInParens()の柔軟性が向上し、TParensTypeの組み合わせが、writeln()で表示可能であれば、正しく機能するようになった。

型の推論

テンプレートパラメータに使用する型をコンパイラが決定することを、型推論と呼ぶ。

上記の最後の例を続けて、コンパイラは関数テンプレートの2つの使用に応じて、次の型を決定する。

コンパイラは、関数テンプレートに渡されるパラメータ値の型からのみ型を推論することができる。通常、コンパイラは型を曖昧さなく推論できるが、プログラマが型を明示的に指定しなければならない場合もある。

明示的な型指定

コンパイラがテンプレートパラメータを推測できない場合がある。この状況は、型が関数パラメータリストに現れない場合に発生する。テンプレートパラメータが関数パラメータに関連していない場合、コンパイラはテンプレートパラメータの型を推測できない。

この例を見るために、ユーザーに質問をし、その回答として値を読み取り、その値を返す関数を設計しよう。さらに、この関数を、あらゆる型の回答を読み取ることができるように、関数テンプレートにしよう。

T getResponse(T)(string question) {
    writef("%s (%s): ", question, T.stringof);

    T response;
    readf(" %s", &response);

    return response;
}
D

この関数テンプレートは、入力からさまざまな型の値を読み込むプログラムで非常に便利だ。例えば、ユーザー情報を読み込む場合、次の行のように呼び出すことを想像できる。

getResponse("What is your age?");
D

残念ながら、この呼び出しでは、テンプレートパラメータTが何を指しているかをコンパイラに伝えることができない。質問がstringとして関数に渡されることはわかっているが、戻り値の型は推測できない。

エラー: テンプレートdeneme.getResponse(T)は、
引数の型からテンプレート関数を推測できない !()(string)
Undefined

このような場合、テンプレートパラメータはプログラマーが明示的に指定する必要がある。テンプレートパラメータは、感嘆符の後に括弧で囲んで指定する:

getResponse!(int)("What is your age?");
D

これで、上記のコードはコンパイラで受け入れられ、関数テンプレートは、テンプレートの定義内でTintの別名として、次のようにコンパイルされる。

テンプレートパラメータが1つだけ指定されている場合、その周囲の括弧は省略可能だ:

getResponse!int("What is your age?");    // 上記と同じ
D

この構文は、これまでのプログラムで使用してきたto!stringと似ている。to()は、変換のターゲット型をテンプレートパラメータとして取る関数テンプレートだ。指定する必要のあるテンプレートパラメータは1つだけなので、通常、to!(string)ではなくto!stringと記述する。

テンプレートのインスタンス化

特定のテンプレートパラメータ値のセットに対するコードの自動生成は、その特定のパラメータ値のセットに対するそのテンプレートのインスタンス化と呼ばれる。例えば、to!stringto!intは、関数テンプレートtoの2つの異なるインスタンス化である。

後で別のセクションで再び述べるように、テンプレートの異なるインスタンス化は、異なる互換性のない型を生成する。

テンプレートの特殊化

getResponse()関数テンプレートは、理論的にはあらゆるテンプレート型に使用できるが、コンパイラが生成するコードは、すべての型に適しているとは限らない。2次元空間上の点を表す次の型があるとする。

struct Point {
    int x;
    int y;
}
D

Point型に対してgetResponse()をインスタンス化することは問題ないが、Pointに対して生成されたreadf()呼び出しはコンパイルできない。これは、標準ライブラリ関数readf()Pointオブジェクトの読み取り方法を知らないためだ。実際にレスポンスを読み込む2行は、関数テンプレートgetResponse()のインスタンス化Pointでは、次のように表示される。

Point response;
readf(" %s", &response);    // ← コンパイルエラー
D

Pointオブジェクトを読み込む1つの方法は、xおよびyメンバーの値を個別に読み込み、その値からPointオブジェクトを構築することだ

特定のテンプレートパラメータ値に対するテンプレートの特別な定義を提供することを、テンプレートの特殊化と呼ぶ。特殊化は、テンプレートパラメータリストの:文字の後に続く型名によって定義される。関数テンプレートgetResponse()Point特殊化は、次のコードのように定義できる。

// 関数テンプレートの一般的な定義(前述と同じ)
T getResponse(T)(string question) {
    writef("%s (%s): ", question, T.stringof);

    T response;
    readf(" %s", &response);

    return response;
}

// Pointに対する関数テンプレートの特化
T getResponse(T : Point)(string question) {
    writefln("%s (Point)", question);

    auto x = getResponse!int("  x");
    auto y = getResponse!int("  y");

    return Point(x, y);
}
D

特化は、getResponse()の一般的な定義を利用して、xおよびyメンバーの値として使用する2つのint値を読み込むことに注意。

テンプレート自体をインスタンス化する代わりに、Point型に対してgetResponse()が呼び出されるたびに、コンパイラは上記の特殊化を使用する。

auto center = getResponse!Point("Where is the center?");
D

ユーザーが11と22を入力した場合:

中心はどこ? (Point)
  x (int): 11
  y (int): 22

getResponse!int()の呼び出しはテンプレートの一般定義に、getResponse!Point()の呼び出しはPointの特殊化にそれぞれ転送される。

別の例として、stringで同じテンプレートを使用することを考えてみよう。文字列の章で覚えているように、readf()は、入力の最後まで、入力からすべての文字を単一のstringの一部として読み込む。そのため、stringの応答を読み込む場合、getResponse()のデフォルトの定義は役に立たない。

// 入力全体を読み込む、名前だけではない!
auto name = getResponse!string("What is your name?");
D

stringに対してテンプレート特化を提供することもできる。次の特化はのみを読み込む:

T getResponse(T : string)(string question) {
    writef("%s (string): ", question);

    // 前回のユーザー入力で残ったと思われる
    // 空白文字を読み込んで無視する
    string response;
    do {
        response = strip(readln());
    } while (response.length == 0);

    return response;
}
D
構造体およびクラステンプレート

Point構造体には、2つのメンバーがintとして明確に定義されているため、小数部の座標値を表現できないという制限がある。この制限は、Point構造体をテンプレートとして定義することで取り除くことができる。

まず、別のPointオブジェクトまでの距離を返すメンバー関数を追加しよう。

import std.math;

// ...

struct Point {
    int x;
    int y;

    int distanceTo(Point that) const {
        immutable real xDistance = x - that.x;
        immutable real yDistance = y - that.y;

        immutable distance = sqrt((xDistance * xDistance) +
                                  (yDistance * yDistance));

        return cast(int)distance;
    }
}
D

このPointの定義は、必要な精度が比較的低い場合に適している:例えば、組織の本社と支社間の距離をキロメートル単位で計算できる:

auto center = getResponse!Point("Where is the center?");
auto branch = getResponse!Point("Where is the branch?");

writeln("Distance: ", center.distanceTo(branch));
D

残念ながら、Pointは、intが提供できる精度を超える精度では不十分だ。

構造体およびクラスは、その名の後にテンプレートパラメータリストを指定することで、テンプレートとして定義することもできる。例えば、Pointは、テンプレートパラメータを指定し、intをそのパラメータに置き換えることで、構造体テンプレートとして定義することができる。

struct Point(T) {
    T x;
    T y;

    T distanceTo(Point that) const {
        immutable real xDistance = x - that.x;
        immutable real yDistance = y - that.y;

        immutable distance = sqrt((xDistance * xDistance) +
                                  (yDistance * yDistance));

        return cast(T)distance;
    }
}
D

構造体およびクラスは関数ではないため、パラメータを指定して呼び出すことはできない。そのため、コンパイラはテンプレートパラメータを推測することができない。構造体およびクラステンプレートのテンプレートパラメータリストは、常に指定する必要がある。

auto center = Point!int(0, 0);
auto branch = Point!int(100, 100);

writeln("Distance: ", center.distanceTo(branch));
D

上記の定義により、コンパイラは、Pointテンプレートのintインスタンス化のためのコードを生成する。これは、以前のテンプレートではない定義と同等だ。しかし、これで、あらゆる型で使用できるようになった。例えば、より精度が必要な場合は、doubleを使用する。

auto point1 = Point!double(1.2, 3.4);
auto point2 = Point!double(5.6, 7.8);

writeln(point1.distanceTo(point2));
D

テンプレート自体は特定の型とは無関係に定義されているが、その単一の定義により、さまざまな精度の点を表現することが可能になる。

Pointをテンプレートに変換するだけで、テンプレートではない定義に従ってすでに記述されているコードでコンパイルエラーが発生する。例えば、getResponse()Point特殊化は、次のようにコンパイルできなくなった。

T getResponse(T : Point)(string question) {  // ← コンパイルエラー
    writefln("%s (Point)", question);

    auto x = getResponse!int("  x");
    auto y = getResponse!int("  y");

    return Point(x, y);
}
D

コンパイルエラーが発生する理由は、Point自体がもはや型ではないためだ。Pointは、構造体テンプレートになった。そのテンプレートのインスタンス化のみが型として扱われる。Pointのインスタンス化に対してgetResponse()を正しく特殊化するには、次の変更が必要だ。

Point!T getResponse(T : Point!T)(string question) {  // 2, 1
    writefln("%s (Point!%s)", question, T.stringof); // 5

    auto x = getResponse!T("  x");                   // 3a
    auto y = getResponse!T("  y");                   // 3b

    return Point!T(x, y);                            // 4
}
D
  1. このテンプレート特殊化がPointのすべてのインスタンスをサポートするためには、テンプレートパラメータリストにPoint!Tを明示的に含める必要がある。これは、getResponse()の特殊化がPoint!Tに対して定義され、Tの値に関わらず適用されることを意味する。この特殊化はPoint!intPoint!doubleなどに一致する。
  2. 同様に、正しい型を応答として返すには、戻り値の型もPoint!Tとして指定する必要がある。
  3. Point!Tのメンバーであるxおよびyの型は、intではなくTになったため、これらのメンバーはgetResponse!int()ではなくgetResponse!T()を呼び出して読み込む必要がある。getResponse!int()Point!intに対してのみ正しいからだ。
  4. 1と2と同様に、戻り値の型はPoint!Tになる。
  5. Point!intPoint!doubleなど、すべての型について型の名前を正確に表示するには、T.stringofを使う。
デフォルトのテンプレートパラメーター

テンプレートを使用するたびにテンプレートパラメータの型を指定するのは面倒な場合がある。特に、その型がほとんどの場合特定の型である場合はなおさらだ。例えば、getResponse()は、プログラムではほとんどの場合int型に対して呼び出され、double型に対して呼び出されるのはごくわずかな場所だけだ。

テンプレートパラメータのデフォルト型を指定することができ、型が明示的に指定されていない場合はその型が使用される。デフォルトのパラメータ型は、=文字の後に指定する。

T getResponse(T = int)(string question) {
    // ...
}

// ...

    auto age = getResponse("What is your age?");
D

上記のgetResponse()の呼び出しでは型が指定されていないため、Tはデフォルトの型intとなり、呼び出しはgetResponse!int()と同等になる。

デフォルトのテンプレートパラメータは、構造体およびクラスのテンプレートにも指定できるが、その場合は、テンプレートパラメータリストが空であっても、必ず記述する必要がある。

struct Point(T = int) {
    // ...
}

// ...

    Point!() center;
D

可変引数の章で見たデフォルトの関数パラメータ値と同様に、デフォルトのテンプレートパラメータは、すべてのテンプレートパラメータに対して、または最後のテンプレートパラメータに対して指定することができる。

void myTemplate(T0, T1 = int, T2 = char)() {
    // ...
}
D

この関数の最後の2つのテンプレートパラメータは指定しなくてもかまわないが、最初のパラメータは指定する必要がある。

myTemplate!string();
D

この使用例では、2番目と3番目のパラメーターはそれぞれintcharになる。

すべてのテンプレートのインスタンス化は、異なる型を生成する

特定の型セットに対するテンプレートのすべてのインスタンス化は、別個の型と見なされる。例えば、Point!intPoint!doubleは別個の型だ。

Point!int point3 = Point!double(0.25, 0.75); // ← コンパイルエラー
D

これらの異なる型は、上記の代入演算では使用できない。

エラー: Point!(double)型の式(Point(0.25,0.75))を
Point!(int)に暗黙的に変換できない
Undefined
コンパイル時機能

テンプレートは完全にコンパイル時機能である。テンプレートのインスタンスは、コンパイラによってコンパイル時に生成される。

クラステンプレートの例: スタックデータ構造

構造体およびクラステンプレートは、データ構造の実装でよく使われる。任意の型を格納できるスタックコンテナを設計しよう。

スタックは最もシンプルなデータ構造の一つだ。これは、紙の束のように要素が概念的に積み重ねられたコンテナを表する。新しい要素は上に追加され、アクセスできるのは最も上の要素のみだ。要素が削除されると、必ず最も上の要素が削除される。

スタック内の要素の総数を返すプロパティも定義すると、このデータ構造のすべての操作は次のようになる。

配列を使用して、配列の最後の要素がスタックの最上位の要素を表すように要素を格納することができる。最後に、任意の型の要素を含むことができるように、クラステンプレートとして定義することができる。

class Stack(T) {
private:

    T[] elements;

public:

    void push(T element) {
        elements ~= element;
    }

    void pop() {
        --elements.length;
    }

    T top() const {
        return elements[$ - 1];
    }

    size_t length() const {
        return elements.length;
    }
}
D
templates.2

このクラス用のunittestブロック(intインスタンス化を使用)は以下の通りだ:

unittest {
    auto stack = new Stack!int;

    // 新しく追加された要素は一番上に表示される必要がある
    stack.push(42);
    assert(stack.top == 42);
    assert(stack.length == 1);

    // .topおよび.lengthは要素に影響を与えてはならない
    assert(stack.top == 42);
    assert(stack.length == 1);

    // 新しく追加された要素は一番上に表示される必要がある
    stack.push(100);
    assert(stack.top == 100);
    assert(stack.length == 2);

    // 最後の要素を削除すると、その前の要素が表示される必要がある
    stack.pop();
    assert(stack.top == 42);
    assert(stack.length == 1);

    // スタックは、最後の要素が削除された際に
    // 空になる必要がある
    stack.pop();
    assert(stack.length == 0);
}
D

このクラステンプレートを利用するために、今回はユーザー定義型で試しよう。例として、Pointを変更したバージョンを以下に示する。

struct Point(T) {
    T x;
    T y;

    string toString() const {
        return format("(%s,%s)", x, y);
    }
}
D

Point!double型の要素を含むStackは、次のように定義できる。

auto points = new Stack!(Point!double);
D

このスタックに10個の要素を追加し、その後1つずつ削除するテストプログラムは以下の通りだ:

import std.string;
import std.stdio;
import std.random;

struct Point(T) {
    T x;
    T y;

    string toString() const {
        return format("(%s,%s)", x, y);
    }
}

// -0.50から0.50までの間のランダムな値を返す。
double random_double()
out (result) {
    assert((result >= -0.50) && (result < 0.50));

} do {
    return (double(uniform(0, 100)) - 50) / 100;
}

// 'count'個のランダムな
// Point!double要素を含むスタックを返す。
Stack!(Point!double) randomPoints(size_t count)
out (result) {
    assert(result.length == count);

} do {
    auto points = new Stack!(Point!double);

    foreach (i; 0 .. count) {
        immutable point = Point!double(random_double(),
                                       random_double());
        writeln("adding  : ", point);
        points.push(point);
    }

    return points;
}

void main() {
    auto stackedPoints = randomPoints(10);

    while (stackedPoints.length) {
        writeln("removing: ", stackedPoints.top);
        stackedPoints.pop();
    }
}
D
templates.2

プログラムの出力からわかるように、要素は追加された順序と逆の順序で削除されている:

操作Point.xPoint.y
加算-0.02-0.01
加算0.17-0.5
加算0.120.23
加算-0.05-0.47
加算-0.19-0.11
加算0.42-0.32
加算0.48-0.49
加算0.350.38
加算-0.2-0.32
加算0.340.27
削除0.340.27
削除-0.2-0.32
削除0.350.38
削除0.48-0.49
削除0.42-0.32
削除-0.19-0.11
削除-0.05-0.47
削除0.120.23
削除0.17-0.5
削除-0.02-0.01
関数テンプレートの例: 二分探索アルゴリズム

二分探索は、既にソートされた配列内の要素を検索する最も高速なアルゴリズムだ。非常にシンプルなアルゴリズムである:中央の要素を検討する。その要素が検索対象の要素であれば、検索は終了する。そうでない場合、検索対象の要素が中央の要素よりも大きい場合は左側の要素、小さい場合は右側の要素に対してアルゴリズムを繰り返し実行する。

初期要素のより小さな範囲で自分自身を繰り返すアルゴリズムは再帰的だ。自身を呼び出すことで、二分探索アルゴリズムを再帰的に記述しよう。

テンプレートに変換する前に、まずintの配列のみをサポートするこの関数を記述しよう。後でテンプレートパラメータリストを追加し、定義内の適切なintTに置き換えることで、簡単にテンプレートに変換することができる。以下は、intの配列で動作する二分探索アルゴリズムだ。

/* この関数は、配列に値が存在する場合、そのインデックスを返す。
 * 存在しない場合はsize_t.maxを返す。 */
size_t binarySearch(const int[] values, int value) {
    // 配列が空の場合、その値は配列に含まれていない。
    if (values.length == 0) {
        return size_t.max;
    }

    immutable midPoint = values.length / 2;

    if (value == values[midPoint]) {
        // 見つかった。
        return midPoint;

    } else if (value < values[midPoint]) {
        // 値は左側のみに存在する;
        // その半分の領域を表すスライスで検索しよう。
        return binarySearch(values[0 .. midPoint], value);

    } else {
        // 値は右辺にしか存在しない;
        // 右辺で検索しよう。
        auto index =
            binarySearch(values[midPoint + 1 .. $], value);

        if (index != size_t.max) {
            // インデックスを調整しよう; 右側のスライスでは
            // インデックスは0から始まる。
            index += midPoint + 1;
        }

        return index;
    }

    assert(false, "We should have never gotten to this line");
}
D

上記の関数は、この単純なアルゴリズムを4つのステップで実装している。

この関数をテストするunittestブロックは次の通りだ。

unittest {
    auto array = [ 1, 2, 3, 5 ];
    assert(binarySearch(array, 0) == size_t.max);
    assert(binarySearch(array, 1) == 0);
    assert(binarySearch(array, 4) == size_t.max);
    assert(binarySearch(array, 5) == 3);
    assert(binarySearch(array, 6) == size_t.max);
}
D

intの関数が実装され、テストされたので、これをテンプレートに変換することができる。intは、テンプレートの定義の中で2箇所だけに登場する。

size_t binarySearch(const int[] values, int value) {
    // ... intはここに表示されない ...
}
D

パラメータリストに現れるintは、要素と値の型である。これらをテンプレートパラメータとして指定することで、このアルゴリズムをテンプレート化し、他の型でも使用可能になる。

size_t binarySearch(T)(const T[] values, T value) {
    // ...
}
D

この関数テンプレートは、テンプレートでその型に適用される操作と一致するあらゆる型で使用できる。binarySearch()では、要素は比較演算子==および<でのみ使用される。

if (value == values[midPoint]) {
    // ...

} else if (value < values[midPoint]) {

    // ...
D

そのため、PointはまだbinarySearch()と一緒に使用することはできない:

import std.string;

struct Point(T) {
    T x;
    T y;

    string toString() const {
        return format("(%s,%s)", x, y);
    }
}

void main() {
    Point!int[] points;

    foreach (i; 0 .. 15) {
        points ~= Point!int(i, i);
    }

    assert(binarySearch(points, Point!int(10, 10)) == 10);
}
D

上記のプログラムはコンパイルエラーを引き起こす:

エラー: 構造体const(Point!(int))を比較するためにメンバー関数
が必要だ
Undefined

エラーメッセージによると、opCmp()Pointに対して定義する必要がある。opCmp()は、演算子オーバーロードの章で説明した。

struct Point(T) {
// ...

    int opCmp(const ref Point that) const {
        return (x == that.x
                ? y - that.y
                : x - that.x);
    }
}
D
まとめ

テンプレートの他の機能については後続の章で説明する。この章で扱った内容は以下の通りだ: