CRTPとテンプレートライブラリ

Curiously Recurring Template Pattern (CRTP)とは、己自身を型パラメータとして実体化したテンプレートクラスを、己自身が継承している様を指す。

このようなテンプレートクラスでは、その型パラメータとして受け取った任意型に対し、(それが任意型であるが故に)、自身の型との、継承関係もしくは互換性といったことを、明示的に指定してやらなくてはならない場合がある。
簡単な親子関係を保持する機構をライブラリとしてまとめておきたい場合を考えよう。親にも子にも、それぞれ任意の型を指定できる汎用的なものにしたいので、テンプレートクラスライブラリにしたい。親は何でもよいし、子も何でもよい。ただ親と子の間にお互いの参照関係をつけるだけのテンプレートクラスライブラリだ。以下のようなものを作ったとする。

template< class C > class Parent
{
public:
  void SetChild( C* ch ){
    m_child = ch;
    ch->SetParent( this );// C2664
  }
  C* m_child;
};
template< class P > class Child
{
public:
  void SetParent( P* p ){
    m_parent = p;
  }
  P* m_parent;
};

利用側のコードは以下のようなものとなる。

class Oya;
class Ko;
class Oya: public Parent< Ko >{};
class Ko: public Child< Oya >{};
void main( void )
{
  Oya* oya= new Oya;
  Ko* ko= new Ko;
  oya->SetChild( ko );
}

ところがこれを利用しようと思うと、上記ライブラリの、//C2664の箇所でエラーとなる!
何故か?それは、SetParent() には、Oya*型を渡さなくてはならないにも関わらず、その基底型、Parent*型であるthisを渡してしまっているためである。
もちろんParent、Childがテンプレートでない普通の基底クラスとして設計すれば、つまり、SetParent()が素直に、Parent*型を受け取るようになっていれば、こんなことは起こらない。問題は、
●Childが任意の型を親とできるようにしたこと
に起因しているのである。。では何故テンプレートにしたのか?それはとりもなおさず、
■任意の型を、親もしくは子として扱えるようにしたかった
からである。
では、を両立させる方法はないのだろうか?
ここで解決策となるのが、CRTPである。
上記SetChildの中のthisは、派生クラスに静的にダウンキャストしてやればよいのである。どうやって?自身の派生型を自身のテンプレートパラメータとして受け取る仕様にしてやればよいだけだ。改良した親子関係規定テンプレートは以下のようになる。

template< class P, class C > class Parent
{
public:
  void SetChild( C* ch ){
    m_child = ch;
    ch->SetParent( static_cast< P* >(this) );// no more C2664
  }
  C* m_child;
};

template< class P, class C > class Child
{
public:
  void SetParent( P* p ){
    m_parent = p;
  }
  P* m_parent;
};

利用側コードは以下のようとなる。main関数は冒頭のものと変わらない。

class Oya;
class Ko;
class Oya: public Parent< Oya, Ko >{};
class Ko: public Child< Oya, Ko >{};

この場合、Oya、Koは、それぞれその基底となるクラスに自身の型を、型パラメタとして与えており、一見奇妙な感じのすることから、CRTPの名前がついた。一見奇妙ではあるが、上記のような問題がこれで解決することを見た。
実際には、Oya, Ko は、下記のようなOyakoTraitsクラスにまとめ、Parentクラス、Childクラスは、OyakoTraits を唯一の型パラメータとして受け取るようにするとよいだろう。

template< class Oya, class Ko > class OyakoTraits
{
 typedef Oya OyaType;
 typedef Ko KoType;
};