テンプレートの制約
テンプレートは通常、オーバーロードされ、 テンプレート引数がテンプレートパラメータに一致する。 テンプレートパラメータは 特殊化を指定できるため、 テンプレート引数は特定の型パターンに一致しなければならない。 同様に、テンプレート値引数は 特定の型に一致するように制約できる。
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にマッチしない
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 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)(); // エラー、マッチしない
参考文献
- 概念(改訂第1版) ダグラス・グレゴアとビャルネ・ストラウストラップ著
DEEPL APIにより翻訳、ところどころ修正。
このページの最新版(英語)
このページの原文(英語)
翻訳時のdmdのバージョン: 2.109.1
ドキュメントのdmdのバージョン: 2.109.1
翻訳日付 :
HTML生成日時:
編集者: dokutoku