我决定测试一下“Effective C ++”中的一个例子,但我没有得到我预期的结果。所以,显然这个(简化的)代码不应该编译:
template <class T>
struct A {
void f(){}
};
template <class T>
struct B : public A <T> {
void f2() { f(); } // calling base function - will not compile
};
以下是解释(为简单起见改变了类名):
上面的代码不会编译,至少不符合一致的编译器。这些编译器会抱怨
f
不存在。我们可以看到f
在基类中,但编译器不会在那里查找它。我们需要了解原因。 问题在于,当编译器遇到类模板
B
的定义时,它们就会出现 不知道它继承了什么类。当然,它是A<T>
,但T
是模板参数, 直到稍后才会知道的(当B
被实例化时)。不知道T
是的,没有办法知道班级A<T>
的样子。特别是,无法知道它是否具有f
功能。
我的编译器(Visual Studio)并不介意......它甚至没有显示任何警告。
以上代码是否正确?
答案 0 :(得分:8)
template <class T>
struct A {
void f(){}
};
template <class T>
struct B : public A <T> {
void f2() { f(); } // calling base function - will not compile
};
在派生模板中,表达式f()
不依赖于任何模板参数,因此编译器会在第一阶段查找期间尝试解析它。此时,模板尚未使用类型进行实例化,编译器不会查看基础A<T>
。原因是编译器无法知道实例化的类型是否存在可能不包含任何成员的A<T>
的特化。
解决方案是使表达式依赖,最简单的方法是使用this->
进行限定:
template <typename T>
void B<T>::f2() { this->f(); }
由于表达式现在是依赖的,因此查找会延迟到第二阶段,其中类型被替换,A<T>
是具体类型。另一种选择是使用定义它的类进行限定:
template <typename T>
void B<T>::f2() { A<T>::f(); }
表达式再次变得依赖,并将在第二阶段解决。主要区别在于,在第二种情况下,调用是合格的,因此它不使用动态调度。如果A<T>::f()
是虚拟的,它仍会执行A<T>::f()
,而不是最终的覆盖。
代码是否正确?不,VS接受吗?是。
这是Visual Studio编译器中已知的不一致,它不实现两阶段查找。它将模板内的所有查找延迟到第二阶段,此时查找成功。