CRTP(Curiously Reccursive Template Pattern / Curiously Reccuring Template Pattern)とは以下のように基底クラスのテンプレート引数として自分自身を代入するテクニックのことを指します.
templateclass Base{/*.....*/}; class C : public Base {/*.....*/};
Boostを始めとする昨今の汎用C++ライブラリではよく見かけるパターンなので,これがどういう使われ方をしているかを適当にまとめてみました.
仮想関数を用いずにコードの再利用を行う
あるクラスの機能の一部を変えてコードの再利用を行う場合,変えたい機能を提供するメンバ関数を仮想関数にしてそれを派生クラスでオーバーライドするのが通常の方法ですが,CRTPを使えば仮想関数を用いずに行うことができます.ただし,もちろんのことですがこのCRTPによるクラスの機能の取替えはコンパイル時に行われるものなので,仮想関数によってなされるような実行時の機能の切り替えは不可能です.
自分で何か良さそうな例を書こうと思ったのですが,あんまり適切な例が思い浮かばないので,このCRTPの使い方の例を載せているページとしてOKA Toshiyuki氏のページをリンクしておきます.
http://www.fides.dti.ne.jp/~oka-t/cpplab-selfref-template.html
http://www.fides.dti.ne.jp/~oka-t/cpplab-selfref-template-2.html
このCRTPの使い方を変形したものとして以下のCRTPの利用法が挙げられると思います.
他の実装に委譲する形式のインターフェースの実装を半自動で行う
クラスに特定のインターフェースを実装するだけで,実装をそのインターフェースに委譲する他の派生的なインターフェースを自動で実装させる,というパターンにCRTPが使われます.
以下,具体的な例を挙げて説明します.
加算を定義したクラスがあるとします.
class C{ public: C operator+(C const &rhs) const { // 実装 } C &operator+=(C const &rhs) { // 実装 return *this; } };
『More Effective C++』の項目22,あるいは『Exceptional C++』の項目20に書いてあるように,通常operator+は以下のようにほぼお決まりのパターンでoperator+=に委譲する形で書くのが原則になっています.
C C::operator+(C const &rhs) const { C tmp(*this); // まぁ,コピーコンストラクタはあるとしましょう tmp += rhs; // ここでoperator+=に委譲している return tmp; }
このoperator+はoperator+=が実装されれば自動で実装されるものと考えることが出来ます.こういった実装の半自動化は整合性・保守性の観点から重要です.
さて,次のようなクラスを作ります.
templatestruct addable{ T operator+(T const &rhs) const { T tmp(static_cast (*this)); tmp += rhs; return tmp; } };
このクラスを利用すると先ほどのclass Cは以下のように書けます.
class C : public addable{ public: C &operator+=(C const &rhs){ // 実装 return *this; } };
先ほどのCの定義との違いがお分かりいただけるでしょうか?たった1行addableから継承するというコードを書くだけで,Cにoperator+を定義することができてしまっています.もちろんCには最低限の実装としてoperator+=の実装は書かなければいけません.しかし,それさえ書いてしまえば後はaddableが自動でCにoperator+というインタフェースを提供してくれるわけです.別の言い方をすれば,addableはoperator+=の実装を色々取り替えられるようにしておいて,それに依存するoperator+の実装を自動で行ってしまうクラスとも言えます.これは『デザインパターン』で言うところのTemplate Methodパターンに該当するかと思います.
特に,このようなaddableの部分がライブラリとして提供されていれば,class Cを定義しようとするユーザはこのようなライブラリを利用して低コストでCの実装を行うことができます.
補足として,このパターンによって自動的に実装できるのは別にメンバ関数に限りません.
template<class T> class swappable { }; template<class T> void swap(swappable<T> &lhs, swappable<T> &rhs) { static_cast<T &>(lhs).swap(static_cast<T &>(rhs)); } class C : public swappable<C> { public: void swap(C &r) { // swapの実装 } };
上の例ではswappableを継承することによってCに対する自由関数void swap(C &, C &)を自動的に実装しています.
このようなCRTPの使い方をしている実例は非常に多いです.例えばBoostから例を挙げてみるとboost::operators(addableの例はここから取らせてもらいました), boost::iterator_facade, boost::iterator_adaptorなどが挙げられると思います.こういうiteratorや演算子といった軽量になる傾向のあるオブジェクトや関数に対しては(仮想関数を持ち出すことはほぼ論外になりますので),このようなCRTPの利用は非常に有効であると思われます.
静的多相性を構成する?
私がCRTPを最初に知ったころは「CRTPを使って静的多相性が出来る!」と思っていたのですが,どうもこの「CRTPによって静的多相性が実現できる」という言い方はおかしいのではないか,と思い直してきました.以下に例を示します.
#include <iostream> template<class T> class base{ public: void method(){ as_derived().method(); } protected: T &as_derived(){ return static_cast(*this); } T const &as_derived() const{ return static_cast<T const &>(*this); } }; class A : public base<A>{ public: void method(){ std::cout << "This is A::method()" << std::endl; } }; class B : public base<B>{ public: void method(){ std::cout << "This is B::method()" << std::endl; } }; template<class T> void some_process(base<T> &r) { r.method(); } int main(int argc, char *argv[]) { A a; some_process(a); B b; some_process(b); return 0; }
上のコードでは,確かにsome_processにおいてrが多相的に扱えています.が,rが多相的に扱えていることとCRTPによってbaseとA, Bに階層が組まれていることは本質的にまったく関係ないと思われるのです.some_processにおいてrが多相的に扱えると言いたいだけなら,以下のコードで良いはずです.
class A{ public: void method(){ std::cout << "This is A::method()" << std::endl; } }; class B{ public: void method(){ std::cout << "This is B::method()" << std::endl; } } template<class T> void some_process(T &r) { r.method(); }
some_processにおいてrが多相的に扱えるための最低限の要求は「"r.method()"というコードがコンパイルを通る(そしてsome_processが期待する意味を持つ)」であって,そこにCRTPが存在する理由は何一つないと思われるのです.
以上から,CRTPの効果という点でより厳密に述べるなら以下のほうが適切ではないか,と考えられます.
汎用関数においてコンセプトの不整合に対するFirewallを構成する
template<class T>; void some_process(base<T> &r); template<class T>; void some_process(T &r);
上の2つの最大の違いは,後者があらゆる型Tに対してsome_processが定義されているように見えるのに対して,前者はbase<T>(の派生型)に対してのみ定義されていることです.
後者の場合,想定していない型,たとえばintに対してもsome_processが定義されているように見えます.が,実際には後者に対してintで実体化するとintのオブジェクトにはr.method()などという構文は定義されていませんので,その部分でエラーを引き起こすことになります.上のようにsome_processが単純な場合ならまだしも,some_processが複雑な場合,コンパイル時に大量の意味不明のエラーが吐き出されエラーの特定が非常に困難になります.皆さんもSTLやBoostを使っていてしばしばこのような経験をしていることと思います.
一方,前者はbase<T>(の派生型)に対してのみ定義されています.base<T>(の派生型)には,some_processにおいて要求されるmethodメンバ関数が必ず定義されているという制約があります.これはsome_processにおける,引数に対するコンセプトの要求が関数の宣言に明示されていると考えることができると思います.これによってsome_processが要求するコンセプトに合わない型,たとえばintなどに対してsome_processを適用しようとするとただ一言「intに対してsome_processは定義されていない」というエラー(もしくはsome_processに対するintのoverloadが解決しないという類のエラー)が吐き出されるだけで済みます.
別の観点から見れば,base<T>のようなクラスはテンプレート(静的多相性)の世界におけるインターフェースクラス(抽象基底クラス)の役割を果たしているとも言えます.
このようなCRTPの作用も非常に重要であると思うのですが,実際にはコードの再利用のためのCRTPの利用に付随してくるだけの効果であることが多く,目立たないものだとは思います.
また,同等の機能を提供する他の機構,たとえばboost::concept_checkなどとの比較も色々考えて見ましたが,あまりまとまった答えは出せませんでした.この部分についてある程度考えがまとまったらまたこのことについて書くかもしれません.
名前空間において汎用関数が受け付ける型を制限する
上とかなり重複するCRTPの効果になると思いますが,タイトルのような効果も期待できます.上に書いたsome_processの二つの宣言
template<class T> void some_process(base<T> &r); template<class T> void some_process(T &r);
では,前者の方がsome_processという関数が受け付ける型の範囲を制限した形になっています.これによって,前者ではsome_processという関数名に対する他のオーバーロードが可能になってきます.
template<class T> void some_process(hoge<T> &r); template<class T> void some_process(huga<T> &r);
もちろんこれらは最も汎用なデフォルトのsome_processの宣言
template<class T> some_process(T &r);
を書いて,以後のsome_processをすべてオーバーロードすることでも可能ですが,これは単にデフォルトを何かに決めるという決定があるだけで結局は前者と同じことになります.
このことは式テンプレートの実装においては特に重要です.というのも,演算子に対して最も汎用的な形を宣言するわけにはいかないからです.
template<class LHS, class RHS> plus<LHS, RHS> operator+(LHS const &lhs, RHS const &rhs); // plusは演算子が返す式オブジェクトです
演算子は他の多数のクラスに対しても定義されるでしょうから,このような最も汎用な宣言を特定のクラスのために書くことが許されません.なので,式テンプレートの実装では演算子が受け付ける型の範囲を制限することが必須になります.
template<class LHS, class RHS> plus<LHS, RHS> operator+(base<LHS> const &lhs, base<RHS> const &rhs);
boost::numeric::ublass, boost::lambda, boost::spiritなど式テンプレートを用いたboostのライブラリではCRTPによるこの効果を享受しています.