最近在看《STL源码剖析》,关于C++类型萃取的问题看了几天,感觉还是有些疑惑。
如果在C++中声明一个“以迭代器所指对象的类型”为类别,应当如何做。C++只支持sizeof(),并未支持typeof()!即便动用RTTI性质中的typeid(),获得的也只是型别名称,不能做变量声明只用。
可以利用function template的参数推导(argument deducation)机制。
1 template <class T>
2 struct MyIter {
3 typedef T value_type;
4 T* ptr;
5 MyIter(T* p=0) : ptr(p){}
6 T& operator*() const {retrun *ptr;}
7 //...
8 };
9
10 template <class I>
11 typename I::value_type
12 func(I ite)
13 { return *ite; }
14
15 //...
16 MyIter<int> ite(new int(8));
17 cout<< func(ite);
以func()为对外接口,却把实际操作全部置于func_impl()之中,由于func_impl()是一个function template,一旦被调用,编译器会自动进行template参数推导。于是导出型别T,顺利解决问题。
迭代器所指对象的型别,称为该迭代器的value type。如果value type用于函数的返回值,上面的方法也就行不通了,毕竟函数的"template参数推导机制"推而导之的只是参数,无法推导函数的返回值。
如果采用内嵌型别,如下:
1 template <class I, class T>
2 void func_impl(I iter, T t)
3 {
4 T tmp;
5 // ...
6 };
7
8 template <class I>
9 inline
10 void func(I iter)
11 {
12 func_impl(iter, *iter);
13 }
14
15 int main()
16 {
17 int i;
18 func(&i);
19 }
func()的返回型别必须加上关键词typename,因为T是一个template参数,在它被编译器具现化之前,编译器对T一无所悉,换句话说,编译器此时并不知道MyIter<T>::value_type代表的是一个型别或是一个member function或是一个data member。关键词typename的用意在于告诉编译器这是一个型别,才能顺利通过编译。
但并不是所有的迭代器都是class type。原生指针就不是,如果不是class type就无法为其定义内嵌性别。C++采用了template partialspecialization(偏特化)。class template专门用来"萃取"迭代器的特性,而value type正是迭代器的特性之一。
1 template <class I>
2 struct iterator_traits{
3 typedef typename I::value_type value_type;
4 };
这个所谓的traits,其意义是,如果I定义有自己的value type,那么通过这个traits的作用,萃取出来的value_type就是I::value_type。换句话说,如果I定义有自己的value type,先前那个func()可以写成这样:
1 template <class I>
2 typename iterator_traits<I>::value_type
3 func(I ite)
4 { return *ite; }
traits可以拥有特化版本,令iterator_traits拥有一个partial specialization如下:
1 template <class T>
2 struct iterator_traits<T*>{
3 typedef T value_type;
4 };
于是,原生指针int*虽然不是一种class type,亦可以通过traits取其value type。traits所扮演的“特性萃取机”角色,萃取各个迭代器的特性。所谓的迭代器特性,指的是迭代器的相应型别(associated types)。
而vector定义如下:
1 template <class T, class Alloc = alloc>
2 class vector {
3 public:
4 typedef T value_type;
5 typedef value_type* iterator;
6 ...
7 }
而使用了两个typedef是为了使其有意义,便于理解,而没有进行类型的萃取。萃取机制是从一个指针萃取出指针所指的型别,而在上述的定义中,是将一个型别取地址,并没有使用iterator_traits进行萃取。