C ++でのメタプログラミング入門

前へ12 3 Page 3 3/3ページ
  • 状態変数:テンプレートパラメーター
  • ループ構造:再帰による
  • 実行パスの選択:条件式または特殊化を使用する
  • 整数演算

再帰的なインスタンス化の量と許可される状態変数の数に制限がない場合、これは計算可能なものを計算するのに十分です。ただし、テンプレートを使用してこれを行うのは便利でない場合があります。さらに、テンプレートのインスタンス化にはかなりのコンパイラリソースが必要なため、大規模な再帰的インスタンス化はコンパイラの速度を急速に低下させたり、使用可能なリソースを使い果たしたりします。 C ++標準では、1,024レベルの再帰的インスタンス化を最小限に許可することを推奨していますが、これは必須ではありません。これは、ほとんどの(すべてではない)テンプレートメタプログラミングタスクに十分です。

したがって、実際には、テンプレートメタプログラムは慎重に使用する必要があります。ただし、便利なテンプレートを実装するためのツールとしてかけがえのない状況がいくつかあります。特に、重要なアルゴリズムの実装からより多くのパフォーマンスを引き出すために、従来のテンプレートの内部に隠すことができる場合があります。

再帰的なインスタンス化と再帰的なテンプレート引数

次の再帰テンプレートについて考えてみます。

テンプレート構造体Doublify {}; テンプレート構造体のトラブル{LongType = Doublifyを使用
   
    ; }; テンプレート構造体トラブル{longType = doubleを使用; }; トラブル:: LongType痛い;
   

使用Trouble::LongTypeだけではなくの再帰的なインスタンス化をトリガーするTroubleTrouble、...、Troubleが、それはまた、インスタンス化しDoublify、ますます複雑な種類を超えます。この表は、それがどれだけ速く成長するかを示しています。

の成長 Trouble::LongType

 
タイプエイリアス 基になるタイプ
Trouble::LongType double
Trouble::LongType Doublify
Trouble::LongType Doublify

   Doublify>

Trouble::LongType Doublify

     Doublify>,

  

     Doublify>>

表が示すように、式の型記述の複雑さは、でTrouble::LongType指数関数的に増大しNます。一般に、このような状況では、再帰的なテンプレート引数を含まない再帰的なインスタンス化よりも、C ++コンパイラにストレスがかかります。ここでの問題の1つは、コンパイラーが型のマングルされた名前の表現を保持することです。このマングルされた名前は、正確なテンプレートの特殊化を何らかの方法でエンコードし、初期のC ++実装では、テンプレートIDの長さにほぼ比例するエンコードを使用していました。これらのコンパイラは、に10,000文字をはるかに超える文字を使用しましたTrouble::LongType

新しいC ++実装では、ネストされたテンプレートIDが最新のC ++プログラムでかなり一般的であり、巧妙な圧縮技術を使用して名前エンコーディングの増加を大幅に削減しているという事実を考慮しています(たとえば、の数百文字Trouble::LongType)。これらの新しいコンパイラは、テンプレートインスタンスに対して実際に低レベルのコードが生成されないため、実際には何も必要ない場合でも、マングルされた名前の生成を回避します。それでも、他のすべての条件が同じであれば、テンプレート引数も再帰的にネストする必要がないように、再帰的なインスタンス化を編成することがおそらく望ましいでしょう。

列挙値と静的定数

C ++の初期には、列挙値は、クラス宣言の名前付きメンバーとして「真の定数」(定数式と呼ばれる)を作成する唯一のメカニズムでした。それらを使用すると、たとえば、Pow3次のように3の累乗を計算するメタプログラムを定義できます。

meta / pow3enum.hpp // 3からN番目のテンプレートを計算するプライマリテンプレートstructPow3 {enum {value = 3 * Pow3 :: value}; }; //再帰テンプレートを終了するための完全な特殊化structPow3 {enum {value = 1}; };

C ++ 98の標準化により、クラス内の静的定数初期化子の概念が導入され、Pow3メタプログラムは次のようになりました。

meta / pow3const.hpp // 3をN番目のテンプレートに計算するプライマリテンプレートstructPow3 {static int const value = 3 * Pow3 :: value; }; //再帰テンプレートを終了するための完全な特殊化structPow3 {static int const value = 1; };

ただし、このバージョンには欠点があります。静的定数メンバーは左辺値です。したがって、次のような宣言がある場合

void foo(int const&);

そして、メタプログラムの結果を渡します。

foo(Pow3 :: value);

コンパイラーはのアドレスを渡す必要があります。これによりPow3::value、コンパイラーは静的メンバーの定義をインスタンス化して割り当てます。その結果、計算は純粋な「コンパイル時」の効果に限定されなくなりました。

列挙値は左辺値ではありません(つまり、アドレスがありません)。したがって、参照によってそれらを渡す場合、静的メモリは使用されません。計算値をリテラルとして渡したのとほぼ同じです。

ただし、C ++ 11ではconstexpr静的データメンバーが導入されており、それらは整数型に限定されません。これらは上記で提起されたアドレスの問題を解決しませんが、その欠点にもかかわらず、メタプログラムの結果を生成する一般的な方法になりました。それらには(人工列挙型とは対照的に)正しい型を持つという利点があり、静的メンバーが自動型指定子で宣言されたときにその型を推測できます。C ++ 17は、インライン静的データメンバーを追加しました。これは、上記で提起されたアドレスの問題を解決し、で使用できますconstexpr

メタプログラミングの歴史

メタプログラムの最初の文書化された例は、Erwin Unruhによるもので、C ++標準化委員会でSiemensを代表していました。彼は、テンプレートのインスタンス化プロセスの計算の完全性に注目し、最初のメタプログラムを開発することによって彼のポイントを示しました。彼はMetawareコンパイラを使用し、連続する素数を含むエラーメッセージを発行するようにそれを誘導しました。1994年のC ++委員会で配布されたコードは次のとおりです(標準の準拠コンパイラでコンパイルされるように変更されています)。

meta / unruh.cpp //素数の計算//(1994年のErwin Unruhによるオリジナルの許可を得て変更)テンプレート
   
     struct is_prime {enum((p%i)&& is_prime2?p:0)、i-1> :: pri); }; テンプレート構造体is_prime {列挙型{pri = 1}; }; テンプレートstructis_prime {enum {pri = 1}; }; テンプレート
    
      struct D {D(void *); }; テンプレート
     
       struct CondNull {static int const value = i; }; テンプレートstructCondNull {static void * value; }; void * CondNull :: value = 0; テンプレート
      
        struct Prime_print {
       

//素数を出力するためのループのプライマリテンプレートPrime_printa; 列挙型{pri = is_prime :: pri}; void f(){D d = CondNull :: value;

// 1はエラー、0は問題ありませんaf(); }}; テンプレート構造体Prime_print {

//ループ列挙型を終了するための完全な特殊化{pri = 0}; void f(){D d = 0; }; }; #ifndef LAST #define LAST 18 #endif int main(){Prime_print a; af(); }

このプログラムをコンパイルするPrime_print::f()と、でdの初期化が失敗したときに、コンパイラはエラーメッセージを出力します。これは、void *のコンストラクターのみがあり、0のみがに有効な変換を行うため、初期値が1の場合に発生しvoid*ます。たとえば、あるコンパイラでは、(他のいくつかのメッセージの中で)次のエラーが発生します。

unruh.cpp:39:14:エラー:「constint」から「D」への実行可能な変換がありませんunruh.cpp:39:14:エラー:「constint」から「D」への実行可能な変換がありませんunruh.cpp:39: 14:エラー:「constint」から「D」への実行可能な変換がありませんunruh.cpp:39:14:エラー:「constint」から「D」への実行可能な変換がありませんunruh.cpp:39:14:エラー:実行可能ではありません「constint」から「D」への変換unruh.cpp:39:14:エラー:「constint」から「D」への実行可能な変換がありませんunruh.cpp:39:14:エラー:「constint」からの実行可能な変換がありません'D'へ

注:コンパイラーでのエラー処理が異なるため、一部のコンパイラーは最初のエラーメッセージを出力した後に停止する場合があります。

本格的なプログラミングツールとしてのC ++テンプレートメタプログラミングの概念は、ToddVeldhuizenの論文「UsingC ++ Template Metaprograms」で最初に普及しました(そしてある程度形式化されました)。Blitz ++(C ++用の数値配列ライブラリ)に関するVeldhuizenの作業でも、メタプログラミング(および式テンプレート手法)に多くの改良と拡張が導入されました。

この本の初版とAndreiAlexandrescuのModernC ++ Designはどちらも、現在も使用されている基本的な手法のいくつかをカタログ化することにより、テンプレートベースのメタプログラミングを活用するC ++ライブラリの爆発的な増加に貢献しました。 Boostプロジェクトは、この爆発に秩序をもたらすのに役立ちました。早い段階で、MPL(メタプログラミングライブラリ)を導入しました。これは、DavidAbrahamsとAlekseyGurtovoyの著書C ++ TemplateMetaprogrammingを通じても普及した型メタプログラミングの一貫したフレームワークを定義しました。

Louis Dionneは、特にBoost.Hanaライブラリを介して、メタプログラミングを構文的にアクセスしやすくするために、さらに重要な進歩を遂げました。 Dionneは、Andrew Sutton、Herb Sutter、David Vandevoordeなどとともに、メタプログラミングのファーストクラスのサポートを言語で提供するための標準化委員会での取り組みを主導しています。その作業の重要な基礎は、リフレクションを通じてどのプログラムプロパティを利用できるかを調査することです。 MatúšChochlík、Axel Naumann、DavidSankelがこの分野の主要な貢献者です。

John J.BartonとLeeR。Nackmanは、計算を実行するときに次元単位を追跡する方法を説明しました。SIunitsライブラリは、WalterBrownによって開発された物理ユニットを処理するためのより包括的なライブラリでした。std::chrono標準ライブラリのコンポーネントは時間と日付のみを扱い、HowardHinnantによって提供されました。