C++でテンプレートプログラミング
C++ for Haskellerにまとまってるけど、自分でも書いてみる。
データ構造をネストした型で定義して、テンプレートの部分特殊化によるマッチングで条件分岐しつつ処理。これが基本戦略。
データ型
データ型は二種類あります。これらは文法で厳密に区別され、メタ型に整数値を代入したりすることはできません(逆も)
- 数値型
- int,char,etc. (floatとかは使えなかった!)
- const char*くらい使えてほしいよねコンパイル時に決定できるんだし><
- メタ型
- プリミティブ
- 構造体
数値型変数を宣言するときは、その型を指定する必要がある。
メタ型変数は型指定する必要がない(できない)。ただしboost/concept_check(あるいは同等のメカニズム)を使えば型(コンセプト)を指定することもできると思う。使ったことないけど。
構造体
変数は、コンストラクタで初期化するもの、定義時に値を指定するものの二種類を宣言することができる。
また、メンバ関数も定義できる。
//メタ型のメンバ変数A,B,Cを持つ構造体hogeを定義する template<typename A,typename B,typename C> struct hoge { }; //hogeのインスタンスを作成し、hoge_instanceという名前に束縛する //メンバA,B,Cはそれぞれint,char,doubleという値に初期化される typedef hoge<int,char,double> hoge_instance; template<typename A,int n> //構築時に値が指定されるメタ型変数Aと数値型変数n struct fuga { typedef char foo; //charという値を持つ変数foo //数値型はなぜかいろんな宣言方法がある static const int x=100; //100という値を持つ enum { y=100; }; }; struct hage { //メンバ関数を定義することもできる(関数については後述) template<typename A> struct do_something; };
関数
関数ではテンプレートマッチングによる分岐、数値型の四則演算、変数の定義が可能。
//引数の値がboolだったらtrueを、そうでなければfalseを返す関数を作ってみましょう //引数Aを取る関数is_boolを定義 template<typename A> struct is_bool { static const bool result=false; //とりあえずfalseを返しておきます }; //特殊化による条件分岐 template<> struct is_bool<bool> { static const bool result=true; //引数がboolのときだけtrueを返す条件を追加 }; //関数の呼び出し const bool i=is_bool<int>::result; //false const bool b=is_bool<bool>::result; //true
数値型変数を引数にする関数も同様に書くことができます
template<int n,int m> struct add { static const int result=n+m; };
実践編:リスト操作
//int型を保持する単方向リストを作ってみましょう template<int Value,typename Next> struct my_list { typedef Next next; static const int value=n; }; struct nil {}; //リストの終端を示す値 //mapでもつくってみましょうかね //まずはプロトタイプ宣言 template<typename List,typename Filter> struct map; //リストを受け取った場合の特殊化。 template<int Value,typename Next,typename Filter> struct map<my_list<Value,Next>,Filter> { //メタ型の変数を返すときはtypenameキーワードがいります。なぜか。 typedef typename my_list<Filter::exec<Value>::result, map<Next,Filter> > result; }; //空リストには空リストを返します template<typename Filter> struct map<nil,Filter> { typedef typename nil result; }; //mapに適用する関数オブジェクト struct add_one { template<int n> struct exec { static const int result=n+1; }; }; typedef my_list<0,my_list<1,my_list<2,my_list<3,nil>>>> l; typedef map<l,add_one>::result l2; //たぶん意図どおりに動いてると思うよ