typenameキーワードの使いどころ

C++でtypenameキーワードを使うケースは以下の2つがある.

  1. テンプレートパラメータを宣言するとき
  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::const_iteratorのように記述すればいいのだが,これにテンプレートパラメータが絡んでくると話が複雑になる.
テンプレートパラメータに依存する名前のことを一般に「依存名」という.C::const_iteratorはクラスの内部で定義された依存名(ネストされた依存名)である.また,これは型名なので,ネストされた依存型名ということができる.
ネストされた依存名はしばしばコンパイル時に問題を引き起こす.例えば,C::const_iterator型へのポインタ変数xを定義するために以下のコードを書いたとする.

template<class C>
void f(const C &container) {
  C::const_iterator *x; // ?
  ...
}

このコードは,実はコンパイラにとっては二通りの解釈ができる.

  1. C内で定義された型であるC::const_iterator型へのポインタ変数xの定義
  2. 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つの場合.

  1. 派生クラスの定義で基底クラスを指定する場合
  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が必要ないのも納得できるだろう.

参考文献