何回も言われていることだけれど、つまづくことが多いところなのでメモしておきます。
以下のようなコードで、コンパイルエラーになるときの対処法です。
template<class U> class Base { public: int f() { return 1; } }; template<class T> class Derived : Base<T> { int g() { return f(); } };
g++では、error: there are no arguments to 'f' that depend on a template parameter, so a declaration of 'f' must be available [-fpermissive]
というメッセージが出ます。
clang++では、error: use of undeclared identifier 'f'
というメッセージが出ます。これは不親切ですね……。
それに加えて、グローバル空間にf()
が定義されていた場合、何の警告も出ずにそっちに誤爆してしまいます。こういうことが起こるとかなり混乱します(デバッガでどの関数が呼ばれているかを確認できれば、バグ取り自体は簡単ですが……)。
このような現象は、以下の条件を全て満たしたとき、起こります。
- クラステンプレートを書いている(今回は、
template<class T>Derived
が該当) - そのクラステンプレートが、テンプレートクラス(今回は、
Base<T>
が該当)を継承している - その継承元のテンプレートクラスのテンプレート引数として、書いているクラステンプレートのテンプレート引数(今回は、
T
が該当)を使っているT
を使っていない場合、たとえばstd::vector<int>
を継承、といった場合は該当しません
- 継承元のテンプレートクラスの変数や関数を使っている場合
- ただし、関数の場合、引数によっては問題ないこともあって、混乱のもととなります
解決方法
this->f()
のようにthis
をつける。あるいは、Base<T>::f()
とする。
なぜ問題が発生するのか
- 何も修飾されていない
f()
について、コンパイラは、Derived
のメンバ関数だと思って探しに行きますが、ありません。 - 継承元クラスのメンバ関数の可能性を検討しますが、
T
が確定していない以上、継承元クラスも確定しません。よって、継承元クラスは検索されません。 - 順々にスコープを広げて前方宣言があるかを探していきますが、グローバル空間まで行ってもやはりありません。
- よってコンパイルエラーになります。
重要なのは、f()
はテンプレート引数T
と無関係な風に見えるということです。
こういう場合、コンパイラはT
が確定していない段階でf()
がどの関数に該当するのかを探しに行きます。
テンプレートの中であってもT
と無関係であれば、C言語と同様、ソースコードの上の方に探しに行きます。
Base
が上の方に書いてあるから見つけられるはず、という風に考えるのは間違いです。なぜなら、T
によってはBase<T>
クラスが特殊化される可能性が残されているからです。
テンプレートクラスがstd::vector<int>
などのように、確定する場合は、2.の部分で継承元クラスが検索されるため、問題が発生しなくなります。
特殊化される可能性が残されている点は同じですが、使った後に特殊化した場合はコンパイルエラーになります。
なぜthis
などで問題が解決するか
this
の型は、Derived<T>*
です。ここには、T
の情報が含まれています。そのような「テンプレート引数に依存したもの」が含まれている場合、T
が確定してからf()
がどの関数に該当するのかを探しに行きます(Two Phase Lookup)。
Base<T>::f()
とする場合も、「テンプレート引数に依存したもの」が含まれているため、T
が確定してからf()
がどの関数に該当するのかを探しに行きます。