0%

C++:模板类中如何调用其模板基类中的函数

问题

下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <string>

template <typename T>
class Base {
public:
virtual const std::string& Method() const = 0;
};

template <typename T>
class Base2: public Base<T> {
public:
void Func() const {
std::cout << "Func:" << Method() << std::endl;
}
};

class Derived: public Base2<double> {
public:
const std::string& Method() const {
static const std::string s("Derived");
return s;
}
};

int main() {
Derived d;
d.Func();
}

在gcc4.1.2下编译会有这样的错误:

1
2
3
test.cpp: In member function ‘void Base2<T>::Func() const’:
test.cpp:14: error: there are no arguments to ‘Method’ that depend on a template parameter, so a declaration of ‘Method’ must be available
test.cpp:14: error: (if you use ‘-fpermissive’, G++ will accept your code, but allowing the use of an undeclared name is deprecated)

原因

C++在对模板类和模板函数进行名字查找时,会分成两次进行:

  1. 对于与模板参数无关的名字,或称无依赖名字,编译器会在看到这个模板的定义时查找名字。
  2. 对于与模板参数有关的名字,或称有依赖名字,编译器会推迟检查,直到模板实例化时再查找名字。

在我们的例子中,Method与模板参数无关,因此是无依赖名字,编译器会在看到Base2定义时查找名字。因为Base是个模板类,在这次查找时还没有实例化,因此编译器不会去Base中查找Method,只会在Base2的定义体中及外围作用域查找Method

上面的例子中,如果Base不是模板类,而是普通类:

1
2
3
4
class Base {
public:
virtual const std::string& Method() const = 0;
};

你会发现编译就正常了。

错误解法1:指定基类类型调用

假如我们调用Method时指定基类类型,这样Method就变成有依赖名字了,是否可行?

1
2
3
4
5
6
7
template <typename T>
class Base2: public Base<T> {
public:
void Func() const {
std::cout << "Func:" << Base<T>::Method() << std::endl;
}
};

编译正常,运行一下:

1
2
3
/tmp/ccgToaEX.o: In function `Base2<double>::Func() const':
test.cpp:(.text._ZNK5Base2IdE4FuncEv[Base2<double>::Func() const]+0x12): undefined reference to `Base<double>::Method() const'
collect2: ld returned 1 exit status

为什么?因为我们指定了Method的类型,对Method的调用就是静态绑定,没有了动态绑定的效果,我们运行的Method就是基类版本,也就是没有定义的那个版本。

正确解法1:using基类中的名字

我们可以手动using基类中的名字,让名字查找时能看到Method,这样还表明MethodBase<T>中的成员,也就意味着Method依赖T,它就是一个有依赖名字,会推迟到实例化时再查找。这样我们仍然能让Method的调用是动态绑定:

1
2
3
4
5
6
7
8
template <typename T>
class Base2: public Base<T> {
public:
using Base<T>::Method;
void Func() const {
std::cout << "Func:" << Method() << std::endl;
}
};

编译正常,运行结果:

1
Func:Derived

完成!

正确解法2:使用this调用

另一种解法是显式使用this,这样也可以将Method变成有依赖名字:

1
2
3
4
5
6
7
template <typename T>
class Base2: public Base<T> {
public:
void Func() const {
std::cout << "Func:" << this->Method() << std::endl;
}
};

编译正常,运行结果:

1
Func:Derived

完成!