英語版
このページの英語版を見る

テンプレートの制約

テンプレートは通常、オーバーロードされ、 テンプレート引数がテンプレートパラメータに一致する。 テンプレートパラメータは 特殊化を指定できるため、 テンプレート引数は特定の型パターンに一致しなければならない。 同様に、テンプレート値引数は 特定の型に一致するように制約できる。

template Foo(T) { ... }
template Foo(T : T*) { ... }
template Foo(T : T[]) { ... }

alias f1 = Foo!(int);   // Foo(T)を選ぶ
alias f2 = Foo!(int*);  // Foo(T : T*)を選ぶ
alias f3 = Foo!(int[]); // Foo(T : T[])を選ぶ

しかし、これには限界がある。多くの場合、 任意でより 複雑な基準がテンプレートで受け入れられるべきものとして存在する。 そのような基準には、以下のようなものがある。

制約は、これを、 引数がパラメータにマッチした後にコンパイル時に評価される ブール式を単純に提供することで解決する。 そのブール値が真であれば、引数とテンプレートが有効にマッチする。 そうでなければ、テンプレートはマッチせず、 オーバーロードのマッチング時にスキップされる。

制約式はテンプレート宣言とif キーワードの後に続く。 は

template Foo(int N)
    if (N & 1)
{
    ...
}

テンプレートFoo を、その 引数が 奇数整数である場合にのみ一致するように制約する。

述語関数

コンパイル時に計算できるものであれば、任意に複雑な条件を使用できる。 例えば、 素数のみを受け入れるテンプレートは次のとおりである。

bool isPrime(int n)
{
    if (n == 2)
        return true;
    // 0、負数、偶数は素数ではない
    if (n < 1 || (n & 1) == 0)
        return false;
    if (n > 3)
    {
        // 奇数整数の分母の可能性を調べる
        for (auto i = 3; i * i <= n; i += 2)
        {
            if ((n % i) == 0)
                return false;
        }
    }
    return true;
}

template Foo(int N)
    if (isPrime(N))
{
    // ...
}

alias f1 = Foo!(5);    // OK、5は素数である
//alias f2 = Foo!(6);    // エラー: Fooにマッチしない
//alias f3 = Foo!(9);    // エラー: Fooにマッチしない
注釈:isPrime は、コンパイル時関数評価を使用して呼び出される。

is

型制約も複雑になる可能性がある。型特化のみでは、 組み込みの浮動小数点型に暗黙的に変換できる型であれば何でも受け入れるテンプレートBar は テンプレートオーバーロードを使用しなければならない。

template Bar(T:float)
{
    ...
}
template Bar(T:double)
{
    ...
}
template Bar(T:real)
{
    ...
}

テンプレートの実装本体は3回複製しなければならない。しかし、制約を使用すれば、 これは1つのテンプレートで指定できる。

template Bar(T)
    if (is(T : float) || is(T : double) || is(T : real))
{
    ...
}

パラメータの特殊化とは異なり、浮動小数点数への暗黙の変換を持つ型は、 異なる制約によって除外することができる。

template Bar(T)
    if (is(T == float) || is(T == double) || is(T == real))
{
    // ...
}

alias b1 = Bar!float; // OK
//alias b2 = Bar!int;   // エラー

IsExpressionを参照して、その他のテストを確認する。

上記の例は、ライブラリモジュールのisFloatingPointテンプレートを使用することで簡略化できる 。 std.traits

import std.traits;

template Bar(T)
    if (isFloatingPoint!(T))
{
    ...
}

__traits

型の特性をテストすることができます。例えば、 型インスタンスを追加できるかどうかなどです。

// T型のインスタンスを2つ追加しようとして、
// T型のインスタンスを追加できた場合に真を返す
const isAddable(T) = __traits(compiles, (T t) { return t + t; });

auto twice(T)(T t)
    if (isAddable!(T))
{
    return t + t;
}

// 追加可能な構造体である
struct S
{
    int i;

    S opBinary(string op : "+")(S s)
    {
        return S(i + s.i);
    }
}

void main()
{
    assert(twice(4) == 8);
    S s = {2};
    assert(twice(s).i == 4);
    //twice("a"); //マッチしない
}

__traits(compiles)関数リテラルが正常にコンパイルできるかどうかを調べるために使用される。 関数リテラルの代わりに、他の式を使用することもできる。 式は評価されない。 他のコンパイル時の__traits も利用可能である

注釈: 上記の関数リテラルは、型T の引数を宣言し、T のインスタンスを構築することなく取得する。

コンパイル時に計算可能な式はすべて 制約として許可されるため、制約を組み合わせることができる。

T foo(T)(T t)
    if (isAddable!(T) && isMultipliable!(T))
{
    return t + t * t;
}

制約は複数のパラメータを処理できる:

template Foo(T, int N)
    if (isAddable!(T) && isPrime(N))
{
    ...
}

より複雑な制約では、 型で実行可能な操作のリストを指定できる。例えば、テンプレートisStack を評価する 場合、型が型プロパティValueType を持つ必要があり、4つの関数 が存在し、そのうち2つの関数は特定の値を返す

const isStack(T) =
    __traits(compiles,
        (T t)
        {
            T.ValueType v = top(t);
            push(t, v);
            pop(t);
            if (empty(t)) { }
        });

template Foo(T)
    if (isStack!(T))
{
    ...
}

制約に基づくオーバーロード

同じ名前を持つオーバーロードテンプレートのリストが与えられた場合、 制約は一致する候補のリストを決定するための「はい/いいえ」フィルターとして機能する。 制約に基づくオーバーロードは、 相互に排他的な制約式を設定することで実現できる。 例えば、オーバーロードテンプレートFoo を、 奇数整数をとり、もう一方が偶数整数をとるように設定する:

template Foo(int N) if (N & 1)    { ... } // A
template Foo(int N) if (!(N & 1)) { ... } // B
...
Foo!(3)    // Aをインスタンス化する
Foo!(64)   // Bをインスタンス化する

上記の2つのテンプレートは、次のようにして組み合わせることができる。 static if

template Foo(int N)
{
    static if (N & 1)
        // Aの本体
    else
        // Bの本体
}

制約は、どちらのテンプレートがより特化されているかを決定する際に考慮されない。

void foo(T, int N)()        if (N & 1) { ... } // A
void foo(T : int, int N)()  if (N > 3) { ... } // B
...
foo!(int, 7)();   // より特殊化されたBを選ぶ
foo!(int, 1)();   // Bの制約に失敗するため、Aを選ぶ
foo!("a", 7)();   // Aを選ぶ
foo!("a", 4)();   // エラー、マッチしない

参考文献