C++ Template (一)
C++ template机制自身是图灵完备的:它可以被用来计算任何可计算的值。于是就有了模板元编程(template metaprogramming),可以编写出”在C++编译期间执行并在编译结束时停止”的程序。
这几篇博客主要用来记录一些模板的基础知识,后面再写一些关于模板元编程的东西。
关于typename的另一种意义
一般的,在以下的声明中,class和typename是没有什么不同的:1
2template<class T> class Foo;
template<typename T> class Foo;
然而C++并不总是把class和typename视为等价。有时候必须要使用typename。我们先来看一个例子,对于下面的函数foo,接收一个C类型的容器:1
2
3
4
5template<typename C>
void foo(const C& container) {
C::const_iterator* x;
...
}
看起来我们好像声明了一个本地变量x,它是一个指向C::const_iterator类型的指针。我们之所以这样认为是因为我们已经知道C::const_iterator是个类型。但如果C::const_iterator不是一个类型呢?如果C刚好有一个static成员变量被命名为const_iterator,而x碰巧是个global变量名字呢?这样的话上面的代码就是一个相乘动作:C::const_iterator乘以x。这完全是有可能的。
在我们知道C是什么类型之前,是没有办法知道C::const_iterator是否是一个类型的。编译器开始解析template foo的时候,也不知道C是什么,C++有个规则可以解析(resolve)这个歧义状态:如果解析器在template中遭遇一个嵌套从属名称时,它就假设这个名称不是一个类型,除非你告诉它是。所以默认情况下嵌套从属名称不是类型。这个规则有个例外,后面会提到。
刚刚出现了一个名词”嵌套从属名称”,现在来解释一下:template内出现的名称如果依赖于某个template参数,就称之为从属名称(dependent name)。如果从属名称在class内成嵌套状,就称他为嵌套从属名称(nested dependent name)。C::const_iterator就是一个从属名称,而且还是个嵌套从属类型名称(nested dependent type name),也就是个嵌套从属名称并且是某个类型。
对于上面的代码,如果希望它按我们的意愿去表达,就需要告诉编译器C::const_iterator是一个类型。只要在它前面加上关键字typename即可:1
2
3
4template<typename C>
void foo(const C& container) {
typename C::const_iterator *x;
}
一般性规则很简单:任何时候当你想要在template中使用一个嵌套从属类型名称时,就必须在紧邻它之前的位置加上关键字typename。这个规则的例外是typename不可以出现在base classes list内的嵌套从属类型名称之前,也不能在member initialization list(成员初始列表)中出现。例如:1
2
3
4
5
6
7
8
9template<typename T>
class Derived: public Base<T>::Nested { //不允许typename
public:
explicit Derived(int x)
: Base<T>::Nested(x) { //不允许typename
typename Base<T>::Nested tmp; //需要加上typename
...
}
};
简而言之
- 在声明template参数时,前缀关键字class和typename是没有区别的。
- 是在用嵌套从属类型名称时需要使用关键字typename来标识;但不要在基类列表和成员初始化列表内使用typename修饰基类。