私は 5 年以上前にテンプレート メタプログラミングを発見し、 Modern C++ Designを読んで大きな刺激を受けましたが、実際にそれを使用する機会を見つけたことはありませんでした。
この手法を実際のコードで使用したことがありますか?
Boostへの貢献者は応募する必要はありません ;o)
私は 5 年以上前にテンプレート メタプログラミングを発見し、 Modern C++ Designを読んで大きな刺激を受けましたが、実際にそれを使用する機会を見つけたことはありませんでした。
この手法を実際のコードで使用したことがありますか?
Boostへの貢献者は応募する必要はありません ;o)
私はかつて C++ でテンプレート メタプログラミングを使用して、幾何学的アルゴリズムで縮退した入力を処理するための「シンボリック摂動」と呼ばれる手法を実装しました。算術式をネストされたテンプレートとして表すことにより (つまり、基本的には解析ツリーを手動で書き出すことにより)、すべての式分析をテンプレート プロセッサに渡すことができました。
この種のことをテンプレートで行うことは、たとえば、オブジェクトを使用して式ツリーを記述し、実行時に分析を行うよりも効率的です。変更された (摂動された) 式ツリーは、コードの残りの部分と同じレベルでオプティマイザーが利用できるため、高速です。そのため、式内だけでなく (可能な場合) 式と周りのコード。
もちろん、式に小さな DSL (ドメイン固有言語) を実装し、変換された C++ コードを通常のプログラムに貼り付けることで、同じことを達成できます。これにより、同じ最適化の利点がすべて得られ、読みやすくなりますが、パーサーを維持する必要があるというトレードオフがあります。
ゲームのグラフィックス コードの内部ループで使用しました。ここでは、ある程度の抽象化とモジュール化が必要ですが、ブランチや仮想呼び出しのコストを支払うことができません。全体として、これは手書きの特殊なケース関数の急増よりも優れたソリューションでした。
テンプレートメタプログラミングと式テンプレートは、抽象化を維持しながら計算作業の一部をコンパイラにオフロードする最適化手法として、科学界で人気が高まっています。結果のコードは大きくなり、読みにくくなりますが、これらの手法を使用して、FEMライブラリの線形代数ライブラリと求積法を高速化しました。
アプリケーション固有の読み物として、ToddVeldhuizenはこの分野の有名人です。人気のある本は、DaoqiYangによる科学者とエンジニアのための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++ のテンプレート メタ言語は、もともと単純な型のパラメーター化用に設計されていましたが、ほとんど偶然にチューリングの完全なメタ言語になりました。したがって、単なる人間ではなく、Andrei Alexandrescu だけが使用できるチューリング ターピットです。
一方、D のテンプレート サブ言語は、実際には、単純な型のパラメーター化を超えたメタプログラミング用に設計されています。Andrei Alexandrescuはそれを気に入っているようですが、実際には彼の D テンプレートを理解できる人もいます。また、誰かが概念実証としてコンパイル時のレイトレーサーを書いたほど強力です。
私が今まで D で書いた中で最も有用で重要なメタプログラムは、テンプレート パラメーターとして構造体型を指定し、ランタイムとして構造体の変数宣言に対応する順序で列ヘッダー名のリストを指定した関数テンプレートだったと思います。パラメーターは、CSV ファイルを読み取り、各行に 1 つずつ構造体の配列を返します。各構造体フィールドは列に対応します。すべての型変換 (string から float、int など) は、テンプレート フィールドの型に基づいて自動的に行われます。
構造体、クラス、および配列を適切に処理するディープ コピー関数テンプレートは、ほとんどの場合は機能しますが、いくつかのケースを適切に処理できません。コンパイル時のリフレクション/イントロスペクションのみを使用するため、本格的なクラスとは異なり、軽量であると想定されているため、D にはランタイム リフレクション/イントロスペクション機能がありません。
テンプレートメタプログラミングを使用するほとんどのプログラマーは、boostなどのライブラリを介して間接的にテンプレートメタプログラミングを使用します。彼らはおそらく舞台裏で何が起こっているのかさえ知らず、それが特定の操作の構文をはるかに簡単にするだけです。
私は DSP コード、特に FFT、固定サイズの循環バッファ、アダマール変換などでかなり使用しました。
Oracle Template Library ( OTL )、boost::any およびLokiライブラリ (Modern C++ Design で説明されているもの) に精通している方向けに、otl_stream の 1 行をコンテナーに格納し、列ごとにvector<boost::any>
データにアクセスできるようにする TMP コードの概念実証を次に示します。番号。そして「はい」、本番コードに組み込むつもりです。
#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;
};
上記のライブラリに慣れていない人向け。
operator >>
OTL の otl_stream コンテナーの問題は、適切な型の変数を宣言し、次の方法で otl_stream オブジェクトに適用することによって、列データに順番にしかアクセスできないことです。
otl_stream o( 1, "select * from blockstatuslist", db );
int rcnum;
int id;
std::string name;
o >> rcnum >> id >> name;
いつも便利とは限りません。回避策は、いくつかのラッパー クラスを作成し、otl_stream からのデータを入力することです。望ましいのは、列の型のリストを宣言できるようにすることです。
olt_stream::operator >>(T&)
Loki のTypelist
構造体、テンプレートの特殊化、および継承の助けを借りて、これらすべてを行うことができます。
Loki のライブラリ コンストラクトの助けを借りて、列の番号 (実際には の型の数) から推測する適切な型の値を返す一連の GetValue 関数を生成することもできますTypelist
。
これを尋ねてからほぼ 8 か月後、ついに TMP を使用しました。基本クラスに QueryInterface を実装するために、インターフェイスのTypeListを使用します。
大規模なステートマシンの場合は、boost::statechart と一緒に使用します。
はい、ほとんどの場合、レガシーAPIをより新しいC++インターフェイスでラップしていたときのダックタイピングに似たいくつかのことを行う必要があります。
いいえ、本番コードでは使用していません。
なんで?
そうしないでください。その背後にある理由は次のとおりです。テンプレート メタプログラミングの性質上、ロジックの一部がコンパイル時に実行される場合、それが依存するすべてのロジックもコンパイル時に実行する必要があります。開始したら、コンパイル時にロジックの一部を実行します。戻り値はありません。雪玉は転がり続け、止める方法はありません。
たとえば、boost::tuple<> の要素を反復処理することはできません。これは、コンパイル時にしかアクセスできないためです。簡単で単純な C++ を実現するには、テンプレート メタプログラミングを使用する必要があります。これは、C++ のユーザーが多くのものをコンパイル時に移動しないように十分に注意していない場合に常に発生します。コンパイル時ロジックの特定の使用法がいつ問題になるかを判断するのが難しい場合もあれば、プログラマーが Alexandrescu で読んだことを熱心に試してテストしたい場合もあります。いずれにせよ、これは私の意見では非常に悪い考えです。
最近までコンパイラのサポートが貧弱だったため、多くのプログラマはテンプレートをあまり使用しませんでした。ただし、過去にはテンプレートに多くの問題がありましたが、新しいコンパイラははるかに優れたサポートを提供しています。私は Mac と Linux の GCC と Microsoft Visual C++ で動作するコードを書いていますが、GCC 4 と VC++ 2005 でのみ、これらのコンパイラが標準を十分にサポートしています。
テンプレートを介したジェネリック プログラミングは、常に必要なものではありませんが、ツールボックスに入れておくと便利なコードであることは間違いありません。
明らかなコンテナ クラスの例ですが、テンプレートは他の多くの用途にも役立ちます。私自身の作品からの2つの例は次のとおりです。