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; //たぶん意図どおりに動いてると思うよ