template< template< class U > T > class CHoge : public T<CFoo> { ..... };
上図の様に、テンプレート引数に template を指定したコードを初めて見たのは1年ほど前だ。
テンプレート引数の中に template を書けるというのは知らなかったし、最初その意味がさっぱり分からなかった。
なんとかそれを理解したはずだったのだが、1年たって上記コードを見ると、その意味をすっかり忘れていた自分がそこにいた。orz
意味を忘れたということは、ちゃんと理解していなかったのかもしれない。
そこで、今度こそ忘れないようにと文書化しておくことにする。
まずはテンプレートの基本を抑えておく。
C++ では以下の構文でテンプレート関数・クラスを定義することができる。
template< テンプレート引数... > 関数定義 または クラス宣言・定義
テンプレートを使って定義された関数・クラスを テンプレート関数、テンプレートクラス と呼ぶ。
よくある例は2つの引数の最大値を返すテンプレート関数だ。
template< typename T > T max(T a, T b) { return a >= b ? a ? b; }
この様に記述しておけば、operator>=() が定義してあるすべての型に対して max() 関数をコールすることが可能になる。
int d = max(3, 2); double d = max(2.1, 5); MyClass t = max(u, v); // u, v の型は MyClass(またはそれに変換可能クラス)
次にテンプレートクラスの例も見ておく。
template< typename T, class Allocator = allocator> class vector { ..... }
上記のコードは STL の vector 定義のはじめの部分。 テンプレート引数は2つあり、最初はコンテナに格納するデータの型。2番目はデータ領域をアロケートするためのクラスだ。 しかも、デフォルトのクラス(class allocator)を指定してあるので、省略することができる。 (って言うか、普通はアロケータクラスは指定しない)
vector<char> v;
利用時は上図のよう利用する。テンプレートクラスの場合、テンプレート関数の様に型を暗に指定するのではなく、
クラス名の直後に < > で囲って型を明示的に指定する。上図では typename T が char に指定されたわけだ。
このようにテンプレート機能により、特定の型に依存しない関数・クラスを定義可能とし、
利用時に型を明示的または暗に指定して特殊化した関数・クラスを実体化することができる。
柔軟かつ効率的なライブラリが定義できるというわけだ。
テンプレート関数を定義しただけでは、コンパイラはコードをいっさい生成しないが、
関数コールを行った時点で初めてコードが生成されることに注意して欲しい。
テンプレートクラスの場合はメンバ関数(コンストラクタも含む)が呼ばれた時点で初めてコードが生成される。
コードが生成されることを実体化と呼ぶ。
テンプレート引数は通常関数定義の仮引数のようなもので、
テンプレート関数・クラス定義に於いて参照可能である。
そしてテンプレート関数・クラスを利用するときに明示的または暗に型が指定されテンプレート引数の値が確定する。
これは通常の関数定義・関数コールと事情はまったく同じである。
int max(int a, int b) // a, b は仮引数 { return a >= b ? a : b; // 仮引数を参照 } void main() { int c = max(1, 23); // 引数に 1, 23 を指定 }
template< typename T, typename U> // T, U は仮引数 class CHoge : public T // 仮引数を参照 { U m_memberVar; // 仮引数を参照 } typedef CHoge<MyClass, int> CMyHoge; // 引数に MyClass, int を指定
さて問題のテンプレート引数に template が指定されている場合の話。
例をもう一度以下に示しておく。
template< template< class U > T > class CHoge : public T<CFoo> { ..... };
“template”が2つ続いていることに惑わされずにテンプレート引数内をよーく見ると、
T は宣言されているだけで、定義されていないことがわかる。
つまり T は引数をひとつ取るテンプレートクラスということをコンパイラに伝えているだけなのだ。
関数の場合でも、以下のように関数を定義せずに、宣言だけを行うことができる。それと同じだ。
int hoge(char, int); // hoge は char, int を引数にとる int 型の関数
つまり、template< class U > T は、T はテンプレートクラスで、テンプレート引数をひとつ取る、ということだ。
「class U」とあるのに、U がどこにも参照されていないので、ますます理解を困難にしているのだが、
実は関数宣言で仮引数名を省略できるのと同じで、テンプレート引数名は省略できる。
先の例は以下のように記述してもよい。たぶん余計な情報が無いコードの方が理解しやすいと思う。
template< template< > T > class CHoge : public T<CFoo> { ..... };
で、テンプレート引数のテンプレートクラス T は、CHoge の中で、規定クラス T<char> として記述されている。
<型> が後置されているのは、T がひとつのテンプレート引数を取るテンプレートクラスだからだ。
「テンプレート」という言葉が頻繁に出てきてわかりづらいかもしれないが理解できたかな?
で、このクラスを使う場合は以下のように、T を指定するわけだ。
typedef CHoge<vector> CHogeVect;
テンプレートクラス定義で、テンプレート引数にテンプレートクラスを指定する意味は何なのだろうか? それは意図的にクラスの自由度を減らし、安全性を高めるだめだと考えられる。
たとえば、コンテナクラスをメンバ変数に持つテンプレートクラス CEditEngine を考える。 CEditEngine を以下のように定義しておけば、コンテナクラスとして何を使用するかを、 vector, deque, list または 自分で作成したコンテナを引数で指定できるようになる。
template< typename TContainer > class CEditEngine { TContainer m_buffer; }; typedef CEditEngine< vector<char> > CEditEngineVctChar; // vector<char> を使用する場合 typedef CEditEngine< vector<wchar_t> > CEditEngineVctChar; // vector<wchar_t> を使用する場合 typedef CEditEngine< list<char> > CEditEngineListChar; // list<char> を使用する場合
CEditEngine で、m_buffer に格納するのは char 型に限定したいとする。その場合クラスユーザに、
typedef CEditEngine< vector<wchar_t> > CEditEngineVctChar; // vector<wchar_t> を使用する場合
template< template<> TContainer > class CEditEngine { TContainer<char> m_buffer; }; typedef CEditEngine< vector CEditEngineVctChar; // vectorを使用する場合 typedef CEditEngine< vector<wchar_t> > CEditEngineVctChar; // vector を使用することはできない typedef CEditEngine< list CEditEngineListChar; // list を使用する場合