C++でtypenameキーワードを使うケースは以下の2つがある.
- テンプレートパラメータを宣言するとき
- テンプレート内にあるネストされた依存型名を指定するとき(例外あり)
テンプレートパラメータの宣言時
ひとつはテンプレートパラメータを宣言するとき.
template<typename T> void f(T t);
これは,以下のようにtypenameの代わりにclassを使った場合でも同じ意味となる.
template<class T> void f(T t);
この場合,typenameとclassのどちらのキーワードを使うかは好みの問題となる.
テンプレートにネストされた型の指定時
もうひとつは,テンプレートパラメータの型にネストされた型を指定するとき.
以下のように,STLコンテナを引数に取り,そのconst_iteratorを使って何らかの操作を行う関数テンプレートを考える.
template<class C> void f(const C &container) { C::const_iterator it(container.begin()); // コンパイルエラー ... }
上のコードはコンパイルエラーとなる.何故だろうか.
ネストされた依存名の曖昧さ
ある型の中にネストされた名前を指定するときは,通常「::」を用いてvector
テンプレートパラメータに依存する名前のことを一般に「依存名」という.C::const_iteratorはクラスの内部で定義された依存名(ネストされた依存名)である.また,これは型名なので,ネストされた依存型名ということができる.
ネストされた依存名はしばしばコンパイル時に問題を引き起こす.例えば,C::const_iterator型へのポインタ変数xを定義するために以下のコードを書いたとする.
template<class C> void f(const C &container) { C::const_iterator *x; // ? ... }
このコードは,実はコンパイラにとっては二通りの解釈ができる.
- C内で定義された型であるC::const_iterator型へのポインタ変数xの定義
- C内で定義されたstaticなデータメンバC::const_iteratorと変数xの積(!?)
2番目の解釈はなかなか無理があるように思えるが,それでもCの詳細が分からない時点ではどちらのケースも考えられる.この問題を解決するために,C++では「テンプレート内にあるネストされた依存名は,特に指定がなければ型名とは解釈されない」というルールがある.つまり,上のコードは2番目の解釈が適用され,CがSTLコンテナの場合はコンパイルエラーとなる.
typenameキーワードによる型名の明示
そこで,型を指定しているということを明示するためにtypenameキーワードを使う必要がある.
template<class C> void f(const C &container) { typename C::const_iterator it(container.begin()); // OK ... }
上のコードは期待通りの動作をする.このように,テンプレート内にあるネストされた依存型名を指定するときはtypenameキーワードを使う必要がある.特に,テンプレートメタプログラミングの際にこの理由でtypenameキーワードを使う場面が多く出てくる(「Boost.MPLでBrainf*ckのプログラムをコンパイル時に動かしてみた - ぬいぐるみライフ(仮)」でもたくさんtypenameを使っている).
typenameを使えないケース
ただし,テンプレートにネストされた依存型名を指定する場合でもtypenameキーワードを使えない(使う必要がない)ケースがある.それは以下の2つの場合.
- 派生クラスの定義で基底クラスを指定する場合
- コンストラクタの初期化リストで基底クラスを指定する場合
以下に例を示す.
class Base { public: class Nested { // ネストされたクラス型 public: explicit Nested(int) {}; }; }; template<class B> class Derived : public B::Nested { // typenameは不要 public: explicit Derived(int x) : B::Nested(x) {} // typenameは不要 }; int main(int argc, const char *argv[]) { Derived<Base> d(10); // OK return 0; }
どちらも型名しか記述できない箇所なので,typenameが必要ないのも納得できるだろう.
参考文献
- Effective C++ 第3版 42項 typenameの2つの意味を理解しよう