実生活でテンプレート メタプログラミングを使用している人はいますか?[閉まっている]
-
09-06-2019 - |
質問
私は発見した テンプレートのメタプログラミング 5年以上前、読書から大きな刺激を受けました 最新の C++ 設計 しかし、実生活でそれを使用する機会は見つかりませんでした。
持っている あなた このテクニックを実際のコードで使用したことがありますか?
貢献者 ブースト 適用する必要はありません;o)
解決
私はかつて C++ のテンプレート メタプログラミングを使用して、幾何学的アルゴリズムで縮退入力を処理するための「シンボリック摂動」と呼ばれる手法を実装しました。算術式をネストされたテンプレートとして表すことによって (つまり、基本的には解析ツリーを手動で書き出すことによって)すべての式分析をテンプレート プロセッサに引き渡すことができました。
テンプレートを使用してこの種のことを実行すると、オブジェクトを使用して式ツリーを作成し、実行時に分析を行うよりも効率的です。変更された (摂動された) 式ツリーがコードの残りの部分と同じレベルでオプティマイザに利用できるため、処理が高速化されます。そのため、式内だけでなく (可能な場合は) 式と式の間の両方でも、最適化の利点を最大限に活用できます。周囲のコード。
もちろん、式に小さな DSL (ドメイン固有言語) を実装し、翻訳された C++ コードを通常のプログラムに貼り付けることで、同じことを実現できます。これにより、同じ最適化の利点が得られ、さらに読みやすくなります。ただし、その代償として、パーサーを維持する必要があります。
他のヒント
「モダン C++ デザイン」で説明されているポリシーが、次の 2 つの状況で非常に役立つことがわかりました。
コンポーネントを開発しているとき、再利用されることが期待されますが、その方法は少し異なります。デザインを反映するためにポリシーを使うというAlexandrescuの提案は、ここで非常によく適合します - これは、「バックグラウンドスレッドでこれを行うことはできるが、後で誰かがタイムスライスでそれを行いたい場合はどうすればよいか」というような質問を乗り越えるのに役立ちます。わかりました、私はConcurrencyPolicyを受け入れ、現時点で必要なものを実装するようにクラスを記述します。そうすれば、少なくとも、私の後任の人が、私の設計を完全にやり直すことなく、必要なときに新しいポリシーを作成してプラグインできることがわかります。警告:時々自分を律する必要がある、そうしないと制御不能になる可能性があります -- 覚えておいてください ヤグニ 原理!
いくつかの類似したコード ブロックを 1 つにリファクタリングしようとしているとき。通常、コードはコピー&ペーストしてわずかに変更されます。そうしないと if/else ロジックが多すぎるため、または関係する型が違いすぎるためです。ポリシーでは、従来のロジックや多重継承では許可されない、クリーンな 1 つに適合するバージョンが許可されることが多いことがわかりました。
私はこれをゲームのグラフィックス コードの内部ループで使用しました。このループでは、ある程度の抽象化とモジュール性が必要ですが、分岐や仮想呼び出しのコストを支払うことができません。全体として、これは手書きの特殊な関数を急増させるよりも優れたソリューションでした。
テンプレート メタプログラミングと式テンプレートは、抽象化を維持しながら計算作業の一部をコンパイラにオフロードする最適化手法として、科学界で人気が高まっています。結果として得られるコードは大きくなり、読みにくくなりますが、これらの手法を使用して、FEM ライブラリの線形代数ライブラリと求積法を高速化しました。
アプリケーション固有の読み取りについては、 トッド・フェルドハイゼン この分野では有名な名前です。人気の本は、 科学者とエンジニアのための C++ およびオブジェクト指向数値コンピューティング ヤン・ダオキ著。
テンプレート メタ プログラミングは、C++ を記述する場合に優れた強力なテクニックです。 図書館. 。カスタム ソリューションで何度か使用したことがありますが、通常は、あまりエレガントではない古いスタイルの C++ ソリューションの方が、コード レビューを通過しやすく、他のユーザーにとっても保守しやすいです。
ただし、再利用可能なコンポーネント/ライブラリを作成するときは、テンプレート メタ プログラミングから多くのメリットを得ることができます。私が話しているのは、Boost の一部のような大規模なものではなく、頻繁に再利用される小さなコンポーネントだけです。
私は、ユーザーが希望するシングルトンのタイプを指定できるシングルトン システムに TMP を使用しました。インターフェイスは非常に基本的なものでした。その下には重いTMPが搭載されていました。
template< typename T >
T& singleton();
template< typename T >
T& zombie_singleton();
template< typename T >
T& phoenix_singleton();
もう 1 つの成功例は、IPC レイヤーの簡素化です。古典的な OO スタイルを使用して構築されています。各メッセージは抽象基本クラスから派生し、いくつかのシリアル化メソッドをオーバーライドする必要があります。極端なことは何もありませんが、大量の定型コードが生成されます。
私たちはそれに TMP を投入し、POD データのみを含むメッセージの単純なケースに対応するすべてのコードの生成を自動化しました。TMP メッセージでは依然として OO バックエンドが使用されていましたが、定型コードの量が大幅に削減されました。TMP はメッセージ ビスタの生成にも使用されました。時間が経つにつれて、すべてのメッセージは TMP メソッドに移行されました。IPC 経由で通常のクラスを送信するための新しいメッセージを派生するよりも、メッセージ受け渡しのためだけに単純な POD 構造体を構築し、TMP にクラスを生成させるために必要な数行 (おそらく 3 行) を追加するほうが簡単でコードも少なくなります。フレームワーク。
私は常にテンプレート メタプログラミングを使用しますが、C++ ではなく D を使用します。C++ のテンプレート メタ言語は、もともと単純な型パラメータ化のために設計されており、ほぼ偶然にチューリング完全メタ言語になりました。したがって、それは単なる人間ではなく、アンドレイ・アレクサンドルスクだけが使用できるチューリング・ターピットです。
一方、D のテンプレート サブ言語は、実際には、単純な型パラメータ化を超えたメタプログラミング向けに設計されています。アンドレイ・アレクサンドルスク 気に入っているようですが、 しかし、他の人は実際に彼の D テンプレートを理解できます。また、誰かが書いたほど強力です。 コンパイル時レイトレーサー 概念実証としてその中にあります。
私がこれまでに D で書いた最も便利で簡単ではないメタプログラムは、テンプレート パラメーターとして構造体の型を指定し、ランタイムとして構造体の変数宣言に対応する順序で列ヘッダー名のリストを指定する関数テンプレートだったと思います。パラメータは CSV ファイルを読み取り、各行に 1 つずつ構造体の配列を返します。各構造体フィールドは列に対応します。すべての型変換 (文字列から浮動小数点、整数など) は、テンプレート フィールドの型に基づいて自動的に行われます。
もう 1 つの優れた関数は、構造体、クラス、配列を適切に処理するディープ コピー関数テンプレートです。これはほとんど機能しますが、いくつかのケースを適切に処理できません。これはコンパイル時のリフレクション/イントロスペクションのみを使用するため、構造体を操作できます。構造体は軽量であると考えられているため、本格的なクラスとは異なり、D には実行時のリフレクション/イントロスペクション機能がありません。
テンプレート メタプログラミングを使用するほとんどのプログラマーは、boost などのライブラリを通じて間接的にそれを使用します。彼らはおそらく舞台裏で何が起こっているのかすら知りませんが、特定の操作の構文がはるかに簡単になることだけを知っています。
私はこれを DSP コード、特に FFT、固定サイズの循環バッファ、アダマール変換などでかなり使用してきました。
Oracle テンプレート ライブラリに精通している人向け (OTL)、boost::any、および ロキ ライブラリ (「最新の C++ デザイン」で説明されているもの) は、otl_stream の 1 行を格納できるようにする概念実証の TMP コードです。 vector<boost::any>
コンテナーを使用し、列番号によってデータにアクセスします。そして「はい」、それを製品コードに組み込むことにします。
#include <iostream>
#include <vector>
#include <string>
#include <Loki/Typelist.h>
#include <Loki/TypeTraits.h>
#include <Loki/TypeManip.h>
#include <boost/any.hpp>
#define OTL_ORA10G_R2
#define OTL_ORA_UTF8
#include <otlv4.h>
using namespace Loki;
/* Auxiliary structs */
template <int T1, int T2>
struct IsIntTemplateEqualsTo{
static const int value = ( T1 == T2 );
};
template <int T1>
struct ZeroIntTemplateWorkaround{
static const int value = ( 0 == T1? 1 : T1 );
};
/* Wrapper class for data row */
template <class TList>
class T_DataRow;
template <>
class T_DataRow<NullType>{
protected:
std::vector<boost::any> _data;
public:
void Populate( otl_stream& ){};
};
/* Note the inheritance trick that enables to traverse Typelist */
template <class T, class U>
class T_DataRow< Typelist<T, U> >:public T_DataRow<U>{
public:
void Populate( otl_stream& aInputStream ){
T value;
aInputStream >> value;
boost::any anyValue = value;
_data.push_back( anyValue );
T_DataRow<U>::Populate( aInputStream );
}
template <int TIdx>
/* return type */
Select<
IsIntTemplateEqualsTo<TIdx, 0>::value,
typename T,
typename TL::TypeAt<
U,
ZeroIntTemplateWorkaround<TIdx>::value - 1
>::Result
>::Result
/* sig */
GetValue(){
/* body */
return boost::any_cast<
Select<
IsIntTemplateEqualsTo<TIdx, 0>::value,
typename T,
typename TL::TypeAt<
U,
ZeroIntTemplateWorkaround<TIdx>::value - 1
>::Result
>::Result
>( _data[ TIdx ] );
}
};
int main(int argc, char* argv[])
{
db.rlogon( "AMONRAWMS/WMS@amohpadb.world" ); // connect to Oracle
std::cout<<"Connected to oracle DB"<<std::endl;
otl_stream o( 1, "select * from blockstatuslist", db );
T_DataRow< TYPELIST_3( int, int, std::string )> c;
c.Populate( o );
typedef enum{ rcnum, id, name } e_fields;
/* After declaring enum you can actually acess columns by name */
std::cout << c.GetValue<rcnum>() << std::endl;
std::cout << c.GetValue<id>() << std::endl;
std::cout << c.GetValue<name>() << std::endl;
return 0;
};
上記のライブラリに詳しくない人向け。
OTL の otl_stream コンテナの問題は、適切な型の変数を宣言し、 operator >>
次の方法で otl_stream オブジェクトに追加します。
otl_stream o( 1, "select * from blockstatuslist", db );
int rcnum;
int id;
std::string name;
o >> rcnum >> id >> name;
必ずしも便利なわけではありません。回避策は、ラッパー クラスを作成し、otl_stream からのデータをそれに設定することです。目的は、列タイプのリストを宣言して、次のようにできるようにすることです。
- 列の型 T を取得します
- その型の変数を宣言する
- 適用する
olt_stream::operator >>(T&)
- 結果を(boost::anyのベクトルに)保存します。
- 次の列の型を取得し、すべての列が処理されるまで繰り返します
ロキの助けを借りてこれらすべてを行うことができます Typelist
構造体、テンプレートの特殊化、および継承。
Loki のライブラリ構造を利用すると、列の番号 (実際には列の型の数) から推測して適切な型の値を返す一連の GetValue 関数を生成することもできます。 Typelist
).
いいえ、製品コードでは使用していません。
なぜ?
- 6 つ以上のプラットフォームをサポートする必要があります ネイティブ プラットホーム コンパイラ。それは 最新のテンプレートは言うまでもなく、この環境でSTLを使用するのに十分難しい 技術。
- 開発者はもう C++ の進歩に追いついていないようです。C++ を使用します 必要なときに。レガシーコードにはレガシーデザインが含まれています。新しいコードは Java、Javascript、Flashなど、他のもので行われます。
この質問をしてからほぼ 8 か月が経ち、ようやく TMP を使用しました。 タイプリスト QueryInterface を基本クラスに実装するためのインターフェイスの。
私は大規模なステートマシンの boost::statechart とともに使用します。
はい、主にレガシー API をより最新の C++ インターフェイスでラップするときにダックタイピングに似た処理を行うためです。
そんなことはしないでください。その理由は次のとおりです。テンプレートのメタプログラミングの性質上、ロジックの一部がコンパイル時に実行される場合、それが依存するすべてのロジックもコンパイル時に実行する必要があります。開始すると、コンパイル時にロジックの一部を実行します。後戻りはできません。雪玉は転がり続け、それを止める方法はありません。
たとえば、boost::tuple<> の要素はコンパイル時にのみアクセスできるため、それらの要素を反復処理することはできません。C++ であれば簡単で単純だったものを実現するには、テンプレート メタプログラミングを使用する必要があります。 いつも この問題は、C++ のユーザーがコンパイル時に多くのものを移動しないよう十分に注意していないときに発生します。コンパイル時ロジックの特定の使用法がいつ問題になるかを判断するのは難しい場合があり、プログラマーは Alexandrescu の本を読んだことを熱心にテストしようとする場合があります。いずれにせよ、これは私の意見では非常に悪い考えです。
最近までコンパイラのサポートが不十分だったため、多くのプログラマはテンプレートをあまり使用していませんでした。ただし、これまでのテンプレートには多くの問題がありましたが、新しいコンパイラーのサポートははるかに優れています。私は Mac と Linux 上の GCC、および Microsoft Visual C++ で動作する必要があるコードを作成していますが、これらのコンパイラがこの標準を十分にサポートしているのは GCC 4 と VC++ 2005 だけです。
テンプレートを使用した汎用プログラミングは常に必要なものではありませんが、ツールボックスに入れておくと間違いなく便利なコードです。
コンテナ クラスの例は明らかですが、テンプレートは他の多くの用途にも役立ちます。私自身の作品からの 2 つの例は次のとおりです。
- スマート ポインター (例:参照カウント、コピーオンライトなど)
- 行列、ベクトル、スプラインなどの数学サポート クラス。さまざまなデータ型をサポートしながらも効率的である必要があります。