今日は、D言語の主な機能の1つを検討します。これは、それが作成されたためです。これは、コンパイル段階での高度なプログラミングです。 一部の人は、C ++またはより複雑な
Lifeゲームの実装で階乗がどのように計算されるかを思い出し、怖がってしまうかもしれません。 Dのテンプレートは、C ++のアナログよりも桁違いにシンプルで強力ですが、それでも特別なアプローチが必要です。したがって、順応のために、素材の複雑さが徐々に増していきます。
問題の声明
Dは、たとえば、
構造型付け (静的型付けのアヒル型付けに似ています)を使用して、たとえば、操作タイプが
foreachステートメントでそれをサポートしているかどうかを確認します。
import std.range; static assert(isInputRange!(uint[]));
static assertは従来の
assertの変形ですが、コンパイル段階で実行され、falseに等しい式が渡されると、コンパイルを停止します。 また、
isInputRangeは 、必要なメソッドの存在をチェックするテンプレートとして宣言されています(次の例に詳しく入ることはできません。すべての概念をさらに検討します)。
template isInputRange(R) { enum bool isInputRange = is(typeof( (inout int = 0) { R r = void;
また、コンパイル時のインターフェイスごとに、1つ以上のチェックパターンを実行する必要があります。 これは少し面倒ですが、次のようにコンパイル時インターフェイスの実装を確認したいと思います。
ここで
isExpose関数を実装し、同時にテンプレートプログラミングを
詳しく調べます。
ウォームアップ
まず、テンプレートの階乗を計算しましょう:
テンプレートを記述するための鍵は、テンプレートの名前と同じ名前を持つ定数またはエイリアスを宣言することです;これは、通常の関数での
戻り値に類似しています。 このテンプレートは、もう1つの内部を使用して(バッテリーを介して)末尾再帰を編成します。
基本的な型、型、型のリスト、そして最も興味深いことに、spiks式の値をテンプレートに転送できます。 値と型を使用すると、すべてが明確になります。これは多くの言語で行われますが、式リストを明確にする必要があります。
template test(T...) {} alias a1 = test!(ulong, float, double);
式リストを使用すると、コンパイル段階で計算できるテンプレートに必要なものを渡すことができます。 全体として、ほぼどこでも式のリストを処理します。
キャラクター操作
必要な
isExposeテンプレートの収集を始めましょう:
allSatisfyテンプレートを見てみましょう。標準ライブラリで
宣言されています:
template allSatisfy(alias F, T...) { static if (T.length == 0) { enum allSatisfy = true; } else static if (T.length == 1) { enum allSatisfy = F!(T[0]); } else { enum allSatisfy = allSatisfy!(F, T[ 0 .. $/2]) && allSatisfy!(F, T[$/2 .. $ ]);
aliasキーワードで宣言される最初のパラメーターとして別のテンプレートを使用します。これは「名前で渡す」ことを意味します。 このキーワードがないと、コンパイラは
Fテンプレートが誤って適用されたことを誓い、
エイリアスを使用して、関数型言語での遅延計算の類似物
を取得します。
allSatisfyは
Tの各要素に
Fを適用し、パターン
Fが
trueを返すたびにチェックし
ます 。
elseブランチで引数のリストを分割するのも奇妙に思えるかもしれ
ません 。 この手法により、リストから1つの要素を直線的に噛み合わせる代わりに、バランスのとれた「呼び出しツリー」を構築するため、コンパイラー保護の応答を無限再帰に大幅に遅らせることができます。 それでも明確でない場合は、図を示します。

ここで、1つのコンパイル時インターフェイスの存在について、型チェックサブタスクを解決する必要があります。 まず、明示的に新しい式リストを作成する機能が必要です。これは、トリッキーなトリックで実行できます。
ここで、コンパイラのヘルプを使用して、インターフェイスメンバ(メソッドとフィールド)のリストを見つけます。
template getMembers(T) {
__traits(allMembers、T)は
、T型のすべての内部要素のリストを返し
ます 。特性の詳細については、
こちらを参照してください 。 これで、コンパイル時インターフェイスのメソッドとフィールドの名前はわかりましたが、これだけでは十分ではありません。インターフェイス要素の名前とチェック対象の型は一致しますが、型は一致しません。 要素タイプを名前にアタッチするには、単純なパイプラインを編成しますが、最初にいくつかの補助テンプレートが必要です。
引数をn回繰り返し、このコピーのリストを返すテンプレート:
コード template staticReplicate(TS...) {
2つのパラメーターを持つテンプレートをリストに適用するテンプレート:
コード template staticMap2(alias F, T...) { static assert(T.length % 2 == 0); static if (T.length < 2) { alias staticMap2 = List!(); } else static if (T.length == 2) { alias staticMap2 = List!(F!(T[0], T[1])); } else { alias staticMap2 = List!(F!(T[0], T[1]), staticMap2!(F, T[2 .. $])); } }
テンプレートのアナログ
折りたたみまたは縮小:
コード template staticFold(alias F, T...) { static if(T.length == 0)
複数の
リストをテンプレートに転送すると、それらは自動的に開かれ、接着され、多くの場合、複数のリストでの操作の実装を妨げます。そのため、サブパターンが明示的に呼び出されたときに開かれる別の「ハード」ラッパーを宣言します:
template StrictList(T...) { alias expand = T; }
このテンプレートでは、
StrictListという名前のエイリアスを宣言していません。このエイリアスを使用すると、このテンプレートをこのエイリアスに自動的に置き換えることはできません。
StrictList!(T、U).expandを呼び出すと、サブパターンとメソッドの類推もできます。TとUからリストを取得します。
前のテンプレートを使用して、最後の補助テンプレートを実装します。 彼は式のリスト(!)のリストを取り、引数の要素が順番に含まれる新しいリストを作成します(関数型言語のzip関数に類似):
コンベアに必要なレンガがすべて揃ったら、その図を描くことができます:

コンベアの最初の部分は次のように実装されます。
alias intMembers = StrictList!(getMembers!Interface); alias intTypes = StrictList!(staticReplicate!(Interface, intMembers.expand.length)); alias pairs = staticMap2!(bindType, staticRobin!(intTypes, intMembers)); private template bindType(Base, string T) { alias bindType = List!(typeof(mixin(Base.stringof ~ "." ~ T)), T); }
インターフェース要素のタイプを取得するために、ドットを介してインターフェース名をメソッド名に接続する
不純物を使用しました。 そして
typeof演算子の助けを借りて、混合物で生成された式のタイプを取得します。 次に、タイプと名前のペアがテストされたクラス/構造に実際に存在するかどうかを確認します。
template checkMember(MemberType, string MemberName) { static if(hasMember!(Type, MemberName)) { enum checkMember = is(typeof(mixin(Type.stringof ~ "." ~ MemberName)) == MemberType); } else { enum checkMember = false; } } enum isExposeSingle = allSatisfy2!(checkMember, pairs);
すべてのパズルのピースが所定の位置に収まり、完全なテンプレートコードが合計されました。
template isExpose(Type, Interfaces...) { private template getMembers(T) { alias getMembers = List!(__traits(allMembers, T)); } private template isExposeSingle(Interface) { alias intMembers = StrictList!(getMembers!Interface); alias intTypes = StrictList!(staticReplicate!(Interface, intMembers.expand.length)); alias pairs = staticMap2!(bindType, staticRobin!(intTypes, intMembers)); private template bindType(Base, string T) { alias bindType = List!(typeof(mixin(Base.stringof ~ "." ~ T)), T); } template checkMember(MemberType, string MemberName) { static if(hasMember!(Type, MemberName)) { enum checkMember = is(typeof(mixin(Type.stringof ~ "." ~ MemberName)) == MemberType); } else { enum checkMember = false; } } enum isExposeSingle = allSatisfy2!(checkMember, pairs); } enum isExpose = allSatisfy!(isExposeSingle, Interfaces); }
そして使用例:
struct CITest1 { string a; string meth1(); bool meth2(); } struct CITest2 { bool delegate(string) meth3(); } struct CITest3 { bool meth1(); } struct Test1 { string meth1() {return "";} bool meth2() {return true;} string a; bool delegate(string) meth3() { return (string) {return true;}; }; } static assert(isExpose!(Test1, CITest1, CITest2)); static assert(!isExpose!(Test1, CITest3));
おわりに
強力なメタプログラミングに基づいて、ボイラープレートコードを排除する便利なDSLまたはテンプレートを作成できます。 このアプローチを実践する素晴らしい例は、
ペグ化されたコンパイル時パーサージェネレーターです。