萃取器相当于对迭代器的功能扩展,有了它让我们得以通过迭代器获得迭代器所指的数据的类型。
现在假设我们要实现一个算法,它接受一个迭代器作为参数,算法中需要定义一个变量,类型为迭代器所指的数据的类型。
对于这个问题,我们并没有什么好办法,C++没有typeof()这种东西,typeid()也只能获得关于类型的信息,并不能用来声明变量。
不过我们还是可以仰仗function templete的参数推导来完成这个任务。
template<class I,class T>
void func_impl(I iter,T t)
{
T tmp;
//do something
}
template<class I>
void func(I iter)
{
func_impl(iter,*iter);
}
int main ()
{
int i;
func(&i);
}
把func()当成一个接口,把实际要做的工作放在func_impl中去做,编译器自动帮我们推导出了T,解决了问题。
可是这种方法有很大的缺陷,首先它很麻烦,其次如果我要把T类型当成函数的返回值,无论怎样这种方法都是没法解决问题的,我们只能另寻出路。
新的方法是在迭代器的定义中做手脚。
template<class T>
struct MyIter
{
typedef T value_type;
T* ptr;
//...
};
template <class I>
typename I::value_type func(I ite)
{
return *ite;
};
在迭代器的定义里加了一行typedef问题就解决了,函数的返回值被设置为typename I::value_type,加typename的原因是告诉编译器value_type是一个类型的名字,否则无法通过编译。
即便这样,问题还是没有完全解决,这种方法只适用于迭代器是一个class的情况下,我们可以在其中typedef,可是如果迭代器是原生指针,我们无法在原生指针中定义什么东西,不过我们可以引入一个中间层,达到我们的目的。
template<class Iterator>
struct iterator_traits
{
typedef Iterator::value_type value_type;
};
template<class T>
struct iterator_traits<T*>
{
typedef T value_type;
};
template<class T>
struct iterator_traits<const T*>
{
typedef T value_type;
};
template<class I>
typename iterator_traits<I>::value_type func(I iter)
{
return *ite;
}
这便是萃取器。对于原生指针,它提供了一个偏特化版本,即当T是原生指针的时候,调用的版本,对他进行了不同的操作,这样使用的时候就不用在乎迭代器是原生指针,还是class了,同理还有const类型的原生指针。
当然,我们要推导的不单单是value_type,还有pointer,reference,differenct_type,iterator_category所以萃取器的完整版是这样的。
template<class I>
struct iterator_traits
{
typedef typename I::itreator_category iterator_category;
typedef typename I::value_type valuetype;
typedef typename I::difference_type difference_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
};
这里解释一下difference_type和iterator_category。
differece type用来表示两个迭代器之间的距离,也可以用来表示一个容器的最大容量,类型一般用内置的ptrdiff_t定义域cstddef,它存在的原因和size_t差不多,虽然底部可能是int或者是uint,不过为了可移植性,所以统一typedef为ptrdiff_t。
iterator_category是迭代器的类型,它的存在相当于告诉了编译器这个迭代器能做到什么功能。
具体的类型有五种
- input iterator 这种迭代器只能读,只能一个一个向前进
- output iterator 只能写,也只能一个一个前进
- forward iterator 可以读可以写,只能前进
- bidirectional iterator 可以读可以写,可以双向移动,不过一次还是只能移动一个单位
- random access iterator 可以读可以写,而且可以进行算数能力,例如+n,-n,<,>等操作
STL中的算法对于所获得的迭代器类型会有不同的做法,比如advanced(),它有两个参数,一个迭代器,一个数值n,他让迭代器向前移动n次。显然不同的迭代器我们不能一视同仁,必须要加以区分。来看看STL的做法。
template<class InputIterator,class Distance>
inline void advancd(InputIterator& i,Distance n)
{
__advance(i,n,iterator_traits<InputIterator>::iterator_category() );
}
这样就根据iterator_category的具体类型生成了一个临时对象,从而转而去调用各自重载的__advance函数。