C++テンプレートメタプログラミング(MPL)とは?

テンプレートメタプログラミングとは、C++のテンプレート(template)を使って、コンパイル時に様々な処理を行ってしまうテクニックです。

テンプレートメタプログラミングのメリット

  1. ビルド時に処理されるため、エラーや不具合を実行時まで持ち越さない -- ビルド時でバッギングが可能
  2. テンプレートによって実現されるため、実行速度が速い。-- マクロが展開されるため最適化されやすい
  3. ユーザーが作ったクラスをプラグインしたり、機能拡張が容易 -- 拡張しても処理速度は落ちない
  4. 通常のプログラミングでは非常に困難な処理を実現できる -- たとえば、コードジェネレーターのような機能も実現可能

テンプレートメタプログラミングのデメリット

  1. ビルド時間が長くなる -- コーディングとデバッグのサイクルがしんどくなる
  2. エラーメッセージが意味不明 -- コンパイラに頑張ってほしいです
  3. 難解なコードになりがち -- 綺麗に書けばなんとか読めます

テンプレートメタプログラミングの例

簡単な例をいくつか紹介します

配列のサイズを得る

配列のサイズを計算するには、#defineマクロとsizeof演算子でも可能です。

#define CountOf(x) (sizeof(x) / sizeof(x[0]))
int hoge[100];
printf("%d", CountOf(hoge));

これで"100"がプリントされます。これをテンプレートを使って実装すると、

template <typename T, size_t N>
size_t CountOf(const T(&)[N]) {
  return N;
}

こうなります。"int hoge[100]"のような単純な配列を与えれば差はありませんが、配列以外のものを渡した場合、#defineだと無条件にエラーになってしまいます。 テンプレートを使えば配列以外の型にも対応が可能なのです。たとえば、STLのコンテナのようなsize()で個数を得られるものに対応するには、

template <typename T>
size_t CountOf(const T& container) {
  return container.size();
}

これでOKです。#defineでは同名でオーバーライドできませんし、テンプレートを使ったほうがスマートですよね。

条件によって変数の型を変える

if_c <[条件], [真の場合], [偽の場合]>

この"if_c"を使って、要素が固定されているデータ配列があり、通常はstd::vector<>を使って実装したいが、要素が1個の場合は単独の定義としたいというHogeClassの実装を行ってみます。

template <size_t N, typename DataType>
struct HogeClass {
  typedef if_c<N==1, DataType, std::vector<DataType> >::type type;
};

例:

HogeClass<1, std::string>::type singleString;
HogeClass<5, std::string>::type vectorString;

if_cの定義

 template <bool C, typename T, typename F> struct if_c            { typedef T type; };
 template <typename T, typename F >        struct if_c<false,T,F> { typedef F type; };

タイプリスト

STLのlistのように、「型」のリストを扱うMPLです。boostにもちゃんとしたlistがありますが、これは概念を理解するための簡易版です。

// NULLクラスの定義
 class NullType {};
 // タイプリスト
 template <typename T, typename U>
 struct Typelist {
   typedef T Head;
   typedef U Tail;
 };
 // タイプリスト定義用のマクロ
 template <typename T1, typename T2, typename T3 = NullType
                                   , typename T4 = NullType
                                   , typename T5 = NullType
                                   , typename T6 = NullType
                                   , typename T7 = NullType
                                   , typename T8 = NullType
 >
 struct list {
   typedef Typelist<T1, Typelist<T2
                      , Typelist<T3
                      , Typelist<T4
                      , Typelist<T5
                      , Typelist<T6
                      , Typelist<T7
                      , Typelist<T8, Typelist<NullType, NullType> > > > > > > > > type;
 };
 // begin, end, next, derefなどの実装
 template <typename Cnt>           struct f_ite { typedef typename Cnt::Head type;  typedef f_ite<typename Cnt::Tail> next; };
 template <typename Cnt>           struct begin { typedef typename f_ite<Cnt> type; };
 template <typename H, typename T> struct endL  { typedef typename endL<typename T::Head, typename T::Tail>::type type; };
 template <typename T>             struct endL<NullType, T> { typedef Typelist<NullType, T> type; };
 template <typename Cnt>           struct end   { typedef typename f_ite<typename endL<typename Cnt::Head, typename Cnt::Tail>::type > type; };
 template <typename Ite>           struct next  { typedef typename Ite::next type; };
 template <typename Ite>           struct deref { typedef typename Ite::type type; };

使用例

 typedef list<ImageBufferRGBA, ImageBufferRGB, ImageBufferGRAY>::type ImageTypeList;
 typedef begin<ImageTypeList>::type ImageTypeListBegin;
 typedef end<ImageTypeList>::type ImageTypeListEnd;
 template <typename Begin, typename End>
 struct ExecuteImageProcT {
   typedef typename deref<Begin>::type Type;
   template <class Proc>
   static void execute(Proc proc, uint32_t fmt) {
     if (Type::PixelFormatType == fmt) {
       proc.execute<Type>();
     }
     else {
       ExecuteImageProcT<next<Begin>::type, End>::execute<Proc>(proc, fmt);
     }
   }
 };
 template <typename End>
 struct ExecuteImageProcT<End,End> {
   template <class Proc>
   static void execute(Proc , PixelFormat) {
   };
 };
 // ここからが使用例
 void hoge(uint32_t format) {
   struct Proc {
     tempalte <typename ImageType>
     void execute() {
        ImageType img;
        // hogehoge
     }
   }
   ExecuteImageProcT<ImageTypeListBegin, ImageTypeListEnd>::execute(Proc(), format);
 }  

これは、ImageBufferXXXXといういくつかのタイプのイメージバッファのリストから、PixelFormatType というピクセルの型が一致した場合に何らかの処理を行わせるものです。このように、型のリストから動的にオブジェクトを生成するなどの処理ができます。これをうまく使うと、virtualを使わずに仮想的な多様性を持たせる事ができます。


[ 戻る ]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-02-02 (火) 07:19:56 (3116d)