在算法设计中,经常需要获取迭代器的相应型别,即迭代器所指对象的类型。C++中的RTTI typeid()可以获取型别的名称,但是无法拿它用来声明对象。
这里总结以下迭代器相应型别的获取方法。
一 利用function template的参数推导机制(argument deducation)
template <class I,class T>
void func_impl(I iter,T t)
{
T tmp;
}
template <class T>
inline void func(I iter){
func_impl(iter,*iter);
}
int main(){
int i;
func(&i);
}
这个方法主要通过对迭代器解引用,得到迭代器所指对象,然后将迭代器和迭代器解引用后的对象,这两个参数传入func_impl函数之中,然后由编译器自动进行模板的参数推导,从而获取迭代器的相应型别。然而这种template参数推导机制方法并不适用于任何的情况,比如函数的返回值就无法利用这种方法,因为template参数推导只适合于参数,不适合于函数返回值。所以我们需要更加全面的方法。
二 声明内嵌型别
先看例子:
#include<iostream>
using namespace std;
template <class T>
struct MyIter{
typedef T value_type;
T* ptr;
MyIter(T* p=0):ptr(p){}
T& operator*() const{
return *ptr;
}
};
template <class I>
typename I::value_type func(I iter){
return *iter;
}
int main(){
MyIter<int>iter(new int(8));
cout<<func(iter);//输出8
}
其中func的返回值必须加上关键字typename,因为T是一个template参数,在它编译具现化之前,编译器对T一无所知,编译器此时并不知道MyIter<T>::value_type代表的是一个型别或者是一个member function或是一个data member。typename的用意是告诉编译器这是一个型别,才能通过编译。但是这种方法只适用于迭代器是一个class或者struct,因为只有class和struct才能使用内嵌型别即typedef T value_type;而对于像指针这种类型,这种方法不使用。怎么办呢,这时候模板偏特化就派上用场了。
三 模板偏特化
模板偏特化的定义:
提供另一份template的定义式,而本身仍然是一个class template,是针对任何template参数更进一步的条件限制所设计出来的一个特化版本。
template <typename T>
class C{
};
template <typename T>
class C<T*>{
};
上面的第二个版本是第一个版本的偏特化,是对T这种任何型别的模板进一步的限制,只能适用于T*这种原生指针类型。
有了偏特化之后,就可以解决上面的原生指针不是class type的类型。我们可以针对“迭代器的template参数为指针”者,设计特化版本的迭代器。
#include<iostream>
using namespace std;
template <class T>
struct MyIter{
typedef T value_type;
T* ptr;
MyIter(T* p=0):ptr(p){}
T& operator*() const{
return *ptr;
}
};
//偏特化
template <class T>
struct MyIter<T*>{
typedef T value_type;
T* ptr;
MyIter(T* p=0):ptr(p){}
T& operator*() const{
return *ptr;
}
};
template <class I>
typename I::value_type func(I iter){
return *iter;
}
int main(){
MyIter<int>iter1(new int(8));
MyIter<int*>iter2(new int(8));//注释掉偏特化版本将出错
cout<<func(iter1)<<endl<<func(iter2);
}
为了方便,我们可以继续添加一个类模板,来萃取迭代器的模板类型。
template<class T>
struct my_iterator_traits{
typedef typename T::value_type value_type;//注意必须有typename
};
//对原生指针T偏特化
template<class T>
struct my_iterator_traits<T*>{
typedef T value_type;
};
//对指向常对象的原生指针偏特化
template<class T>
struct my_iterator_traits<const T*>{
typedef T value_type;
};
第二个和第三个版本可以萃取处原生指针的相应类型,对于const T*类型如果没有定义第三个版本,则第二个版本会把它萃取成const T。为了更好的萃取T类型,于是就定义了第三个版本。
下面是完整的程序:
#include<iostream>
using namespace std;
template <class T>
struct MyIter{
typedef T value_type;
T* ptr;
MyIter(T* p=0):ptr(p){}
T& operator*() const{
return *ptr;
}
};
//偏特化
template <class T>
struct MyIter<T*>{
typedef T value_type;
T* ptr;
MyIter(T* p=0):ptr(p){}
T& operator*() const{
return *ptr;
}
};
template<class T>
struct my_iterator_traits{
typedef typename T::value_type value_type;//注意必须有typename
};
//对原生指针T偏特化
template<class T>
struct my_iterator_traits<T*>{
typedef T value_type;
};
//对指向常对象的原生指针偏特化
template<class T>
struct my_iterator_traits<const T*>{
typedef T value_type;
};
template <class I>
typename my_iterator_traits<I>::value_type func(I iter){
return *iter;
}
int main(){
MyIter<int>iter1(new int(8));
MyIter<int*>iter2(new int(8));
cout<<func(iter1)<<endl<<func(iter2);
}
若要让my_iterator_traits有效工作,必须对所有的迭代器都定义相应型别即value_type。各自都定义一个内嵌类型。不定义的就无法进入STL这个体系。
STL中常见的5中相应型别如下:
template<class T>
struct my_iterator_traits{
typedef typename T::iterator_category iterator_category;//迭代器分类
typedef typename T::value_type value_type;//相应型别
typedef typename T::difference_type difference_type;//两个迭代器之间的距离
typedef typename T::pointer pointer;//指针
typedef typename T::reference reference;//引用
};