类型萃取
推荐反复阅读第三部分反思与总结 ,真的体会到了第三部分表达的意思后,第一、二部分走马观花也无妨。
目录
一、 背景
在算法中运用迭代器时,很可能会用到相应类型(associated type)。什么是相应类型?迭代器所指之物的类型便是其中之一。假设算法中有必要声明一个变量,以“迭代器所指对象的类型”为类型,如何处理?
--------参考文献 侯捷.STL源码剖析[M].武汉:华中科技大学出版社,2002.6:84-85.
二、 解决办法
2.1 函数模板类型自推
利用function template的参数自推(argument deducation)机制。为理解存在的局限性将在稍后举例说明。
所谓 "自推" 指的是编译器在某些情况下,可以根据调用方提供的信息来补全用户未提供的模板参数,是模板实例化 (template instantiation) 的一个步骤,发生的时机是在函数模版调用时 (invoke time of function template)。也就是说,当需要的时候,每次模版函数的调用,均会 (根据调用方提供的信息) 触发一次潜在的模板参数类型推导。
#include<iostream>
using namespace std;
/*一个功能不完备的迭代器,但用于举例已经足够了,只需要实现deference运算符重载*/
template<class Item>
class iter
{
public:
typedef Item* pointer;
typedef Item& reference;
public:
iter(pointer ptr = 0) //默认构造方法
:_ptr(ptr){ }
reference operator*() const { return *_ptr; } //dereference
private:
pointer _ptr;
};
template<class I, class T>
void func_impl(I iter, T type)
{
T output = *iter;
cout << "利用函数模板类型自推:";
cout << output << endl;
}
template<class I>
inline
void func(I iter) //func函数实际上是对外接口
{
func_impl(iter, *iter);
}
int main()
{
int i = 10;
func(&i);
return 0;
}
以func()为对外接口,却把实际操作全部置于func_impl()。由于 func_impl()是一个function template o,一旦被调用,编译器会自动进行template参数推导。于是获得类型T(可拿来做变量声明之用)。
探讨局限性就暂时用以下三段代码
代码一
template <typename T>
T foo(T ) { return T(0); }
int main()
{
int a;
foo(a);
}
从刚刚的代码我们可以知道:程序中传入一个int类型的变量,编译器自动识别出该类型是int类型。关键在于——参数类型列表(parameter type list)必须存在。将上述代码稍作改动,就能得到不一样的结果。
代码二
template <typename T>
T foo() { return T(0); }
int main()
{
int a = foo(); //明确类型
foo(); //不明确类型!!!
}
函数模板类型自推是有局限性的,对于上述代码,试图通过返回值类型推导出T到底是什么类型是可取的。但,如果函数返回值类型为void,参数类型列表(parameter type list)为空,那么无论怎样都无法处理,需要另辟蹊径。引出了声明内嵌类型这一手段。
“为了与C保持兼容,返回值并非是调用函数时的必要条件,因此函数模版类型推导和函数重载都不能且不应依赖返回值。”
代码三
template<class I, typename value_type>
value_type func_impl(I iter, value_type value)
{
value_type output = value;
return output;
}
int main()
{
int i = 10;
cout << func_impl(&i, i) << endl;
return 0;
}
乍一看代码三,我们似乎觉得,这不是可以使用函数模板类型自推确定迭代器所指之物的类型定义函数返回值类型吗?这并不矛盾,如果参数类型列表为空,怎么办??? 我们说的是,对于返回值类型是迭代器所指之物的型别,使用函数模板类型自推是有局限性的。再次强化了代码二所指出的那样。
2.2 声明内嵌类型
typedef T value_type; //声明内嵌类型
迭代器所指之物的类型已经是内嵌在迭代器中,我们就可以像普通类型一样去使用。
#include<iostream>
using namespace std;
template<class T>
class iter
{
public:
typedef T* pointer;
typedef T& reference;
typedef T value_type; //声明内嵌类型
public:
iter(pointer p = 0)
:_ptr(p){ }
reference operator *()const
{
return *_ptr;
}
private:
pointer _ptr;
};
template<class I>
typename I::value_type //返回值类型
func(I ite)
{
return *ite;
}
int main()
{
int a = 10;
iter<int> ite(&a);
cout << func(ite) << endl;
return 0;
}
但并不是所有的迭代器都是类,原生指针(类型* 变量名,可以理解为一般的指针)也是迭代器,而其没有成员这种说法,还是需要另辟蹊径。
2.3 类型萃取机
适用于类的萃取机
#include<iostream>
using namespace std;
template<class I> //模板 0号
struct miterator_traits
{
typedef typename I::value_type value_type;
};
Partial Specialization(偏特化) :如果 class template 拥有一个以上的template 参数,我们可以针对其中某个(或数个、但非全部) template 参数进行特化工作。换句话说,我们可以在泛化设计中提供一个特化版本(也就是将泛化版本中的某些 template 参数赋予明确的指定)。
--------参考文献 侯捷.STL源码剖析[M].武汉:华中科技大学出版社,2002.6:86-87.
template<class T>
struct miterator_traits<T*> //1号
{
typedef T value_type;
};
int main()
{
miterator_traits<const int*>::value_type test1;
test1 = 2; //会报错!!!常量不可修改
return 0;
}
体会:在代码中出现的1号萃取机参数类型是const int*,经过萃取之后,T经过编译器确认是const int。如果我们需要提取出类型int(non-const),如何处理???
template<class T> //2号
struct miterator_traits<const T*>
{
typedef T value_type;
};
int main()
{
miterator_traits<const int*>::value_type test1;
test1 = 2;
cout << test1 << endl;
system("pause");
return 0;
}
体会:在代码中出现的2号萃取机参数类型是const int*,经过萃取之后,T经过编译器确认为int,这正是我们所需要的。
三、 反思与总结
1、类型萃取机为什么会存在?
迭代器主要分两类:一个是类去实现(常见如iterator),另一个是以原生指针实现。
函数模板类型自推 解决了推导参数类型列表的问题,但没有完全解决其他问题,如定义返回值类型等。这个时候就出现了内嵌类型声明;
内嵌类型声明 解决了迭代器是以类实现的情况,无法解决迭代器以原生指针实现的情况。就出现了萃取机;
类型萃取机 出现的目的主要是为了解决原生指针类型萃取所存在的问题(因为手段2无法解决),原生指针的问题解决了之后,把类去实现的迭代器也作为类型萃取机的操作对象的话,类型萃取机就能处理所有的迭代器了(类型萃取机就该做到这一点)。而需要处理类实现的迭代器相应类型的提取,只不过多了一层间接性而已。正如代码中0号萃取机所展示的那样。
2、示意图