现在的位置: 首页 > 综合 > 正文

被误解的C++——优化variant实现

2012年12月11日 ⁄ 综合 ⁄ 共 15200字 ⁄ 字号 评论关闭
优化variant实现
上一次,我大概制作了一个variant类型,并设法赋予这个类型同C++内置类型几乎一样的行为。但是,具体实现起来,倒是有点望而生畏。想想看,如果我的variant需要包容5种类型,那么单单一个操作符,就需要5×5+1=26个操作符重载(那单独一个是variant类型操作数的重载)。所有二元操作符都是如此。
通过蛮力来实现variant,尽管可能,但着实愚蠢。我们必须寻找更简单有效的实现途径,避免为了一个“屁眼大的”variant(请原谅我说粗话)写上几万行代码,而且这些代码就像一窝小猪仔那样相像。好在C++为我们提供了充足的现代武器,使我们拥有足够的火力摆平这些问题。
让我们先从操作数都是variant的二元操作符入手:
variant  operator+( const variant& v1, const variant& v2) {…}

简单起见,先考察operator+的实现,然后扩展到其他操作符。
由于操作数是variant类型,那么它们可能代表不同的类型。我们必须知道操作数的实际类型,才能对其实施相应的+操作。最传统的办法就是使用switch:
variant operator+(const variant& v1, const variant& v2) {
switch(v1.get_type_code())
{
case vt_double:
switch(v2.get_type_code())
{
case vt_double:
…;
break;

}
case vt_int:
switch(v2.get_type_code())
{
case vt_double:
…;
break;

}

}
}
好家伙,又是一个组合爆炸。一步步来,我们先来处理这堆讨人嫌的switch…case…。一般而言,对于一个函数(操作符)内的的大量分派操作,可以使用包含函数指针的数组或者容器替代。如果标记值(这里的vt_...)是连续的,可以直接使用数组;如果标记值不连续,可以使用关联容器。这里vt_...是连续的,所以用数组比较方便:
typedef variant (*add_op_t)(const variant& v1, const variant& v2);
add_op_t tbl_type_ops[3][3];//函数指针表,假设variant对应三种类型
variant add_op_double_double(const variant& v1, const variant& v2){…}
variant add_op_double_int(const variant& v1, const variant& v2){…}

variant add_op_int_double(const variant& v1, const variant& v2){…}

tbl_type_ops [vt_double][vt_double]=add_op_double_double;
tbl_type_ops [vt_double][vt_int]=add_op_double_int;

variant operator+(const variant& v1, const variant& v2) {
returntbl_type_ops [v1.get_type_code()][v2.get_type_code](v1, v2);
}
operator+的代码是简单了,但是它的代码实际上转嫁到每个专用操作函数add_op_...上去了。并没有简化多少。下一步,我们来处理这些add_op_...:
template<typename VT1, typename VT2>
variant add_op(const variant& v1, const variant&v2) {
throwexception(string(“cannot add type ”)+typeid(VT1).typename()
+”to”+typeid(VT2).typename());
}//主函数模板,对应不兼容类型的操作。抛出异常。
template<>
variant<double, double> add_op(const variant& v1, const variant&v2) {
returnvariant(v1.dbval+v2.dbval);
}//针对double+double的操作

tbl_type_ops [vt_double][vt_double]=add_op<double, double>;
tbl_type_ops [vt_double][vt_int]=add_op<double,int>;

利用函数模板,及其特化,消化掉一部分的冗余代码。利用主函数模板实现所有不能互操作的类型操作,而可操作的类型则使用特化的模板实现。当然,冗余代码还是存在,这部分我们一会儿再处理。先来看看tbl_type_ops的填充。这部分代码也存在组合爆炸。为消除这个问题,我请出了模板元编程(TMP)。当然,我没有那么好的本事去直接倒腾TMP,我“借用”了boost::mpl::vector来实现这步优化:
//使用mpl::vector存放variant包容的类型
typedef boost::mpl::vector<double, int, string>op_types;
const int n_types=boost::mpl::size<op_types>::value;
//操作函数指针表
typedef variant (*add_op_t)(const variant& v1, const variant& v2);
add_op_t tbl_type_ops[n_types][n_types];
//填充函数指针表单个元素
template<int m, int n>
inline void set_tbl_type() {
typedefmpl::deref<mpl::advance<mpl::begin<op_types>::type, 
mpl::int_<m> >::type>::typetype_1;
typedefmpl::deref<mpl::advance<mpl::begin<op_types>::type, 
mpl::int_<n> >::type>::typetype_2;

tbl_type_ops [m][n]=add_op<type_1, type_2>;
}
//填充函数指针表单元的函数对象类
template<int m, int n>
struct fill_tbl_types_n
{
void operator()() {
set_tbl_type<m-1, n-1>();//填充函数指针单元
fill_tbl_types_n<m, n-1>()();//递归
}
};
template<int m>
struct fill_tbl_types_n<m, 0>//特化,递归结束
{
void operator()() {}
};
//填充函数指针表行的函数对象类
template<int m, int n>
struct fill_tbl_types_m
{
void operator()() {
fill_tbl_types_n<m, n>()();//创建并调用fill_tbl_types_n函数对象
fill_tbl_types_m<m-1, n>()();//递归
}
};
template<int n>
struct fill_tbl_types_m<0, n>//特化,递归结束
{
void operator()() {}
};
void fill_tbl_op() {
fill_tbl_types_m<n_types, n_types>()();
}
这里运用函数对象类模板的特化,构造了函数指针表的填充自动函数。在需要时,只需调用fill_tbl_op()函数即可。该函数中创建fill_tbl_types_m<n_types, n_types>函数对象,然后调用。这个函数对象的operator()首先创建并调用fill_tbl_types_n<m, n>函数对象。后者先调用set_tbl_type<m-1, n-1>模板函数,执行填充tbl_type_op数组的[m-1, n-1]单元格。然后递归调用fill_tbl_types_n<m, n-1>函数对象。直到n-1==0,编译器便会选择特化版本的fill_tbl_types_n<m, 0>函数对象。该特化的operator()操作符重载是空的,因此递归结束。这样完成一行的填充。然后,fill_tbl_types_m<m, n>则递归调用fill_tbl_types_m<m-1, n>函数对象,填充下一行。直到调用fill_tbl_types_m<0, n>特化版本,结束递归。
现在需要仔细看一下set_tbl_type<>函数模板。该模板上来就是两个typedef。这两个typedef创建了两个类型别名,分别用m和n做索引,从boost::mpl::vector<double, int, string>中取出相应的类型:
typedefmpl::deref<mpl::advance<mpl::begin<op_types>::type, 
mpl::int_<m> >::type>::typetype_1;

头晕是吧。我的头还有点晕呢。这就是模板元编程,不停地鼓捣类型。具体的操作可以参考boost文档或《The Template Meta-programming》一书,我这里就不多说了,反正就是从一个存放类型的vector中取出所需的类型。
这样获得的两个类型用来实例化add_op<>()模板函数,并且填充到tbl_type_ops[m][n]元素中。
这样,利用TMP和GP两种强大的机制,消除了tbl_type_ops填充的组合爆炸问题。如果我们需要向variant中加入新的类型,那么只需在mpl::vector<double, int, string>中直接加入类型即可:
typedef mpl::vector<double, int, string, bool, datetime>op_types;
OK,下面回过头,来处理add_op<>中存在的组合爆炸。对于每一对可以直接或间接相加的类型,都需要做一个add_op<>的特化版本。这当然不够好。我们可以进一步抽象add_op,然后加以优化。我把整个add_op<>模板改写成如下代码:
template<typename VT1, typename VT2>
variant add_op(const variant& v1, const variant& v2) {
typedeftype_ret<VT1, VT2>::typeRetT;
returnvariant(v1.operator RetT()+v2.operator RetT());
}
这里,我首先利用type_ret模板(模板元函数)获得两个操作数相加后应有的返回类型。这个模板一会说明。然后,调用variant上的类型转换操作符,将两个操作数转换成返回类型。最后相加,并创建返回variant对象。代码非常简单,没法再简单了。
再来看看type_ret<>:
template<typename T1, typename T2>
struct type_ret
{
typedefT1type;
};
template<>
struct type_ret<int, double>
{
typedefdoubletype;
};
template<>
struct type_ret<string, double>
{
typedefdoubletype;
};
…//其他类型对的返回类型
type_ret<>是典型的模板元函数,没有任何实际代码,只有编译时计算的typedef。主模板将第一个类型参数typedef出一个别名。其后的模板特化对于一些特殊的情况做出定义,如int和double相加返回第二个操作数类型double(即所谓的类型提升)。
我们现在已经优化了variant+varint的代码。现在来看看如何优化variant类型和其他类型的加法:
template<typename T>
variant operator+(const variant& v1, const T& v2) {
returnv1+variant(v2);
}
template<typename T>
variant operator+(const T& v1, const variant& v2) {
returnvariant(v1)+v2;
}
这非常简单,直接利用了variant+variant,将其它类型的操作数转换成variant类型,然后相加。

----------------------------------------------------------------------

好,加法完成了。但还有其他操作符。每个操作符都做那么一个函数指针表,也不见得高明到哪里去。现在需要整合优化这些操作符。这里,我想到了两种方法:一种是将函数指针表和填充操作整个地封装在一个模板中,模板参数采用int op形式。每一种操作符对应一个整数(或枚举值),并利用某种手段(如singleton)唯一生成一组全局的函数表,以此处理每一种操作。另一种方法是为函数指针表加一个维度(二维扩展到三维),新的维度对应不同的操作符。前一种方法灵活性强些,而且有利于性能优化;而后一种方法实现简单。这里我使用后一种方法:
enum
{
vt_op_add=0,
vt_op_add_assign=1,
vt_op_equal=2,
vt_op_not_equal=3

};
const int vt_op_num=10;

template<typename T, int op>
struct var_op;

template<typename T>
struct var_op<T, vt_op_add>
{
T operator()(const T& v1, const T& v2) {
returnv1+v1;
}
}

template<typename T>
struct var_op<T, vt_op_equal>
{
bool operator()(const T& v1, const T& v2) {
returnv1==v1;
}
}

template<typename VT1, typename VT2, int op>
variant variant_op(const variant& v1, const variant& v2) {
typedeftype_ret<VT1, VT2>::typeRetT;
returnvariant(var_op<RetT,op>()(v1.operator RetT()+v2.operator RetT()));
}
我使用了一个函数对象模板var_op<>抽象各种算法(二元)。针对每一种运算符特化。抽象的variant_op函数模板实例化var_op<>,然后调用。获得相应的操作。
观察variant_op的模板参数,会发现已经包含了一个操作的基本要素。(眼下这个形式正好符合逆波兰表达式)。
接下来,只需将函数指针数组,及其填充算法加以扩展,便可大功告成:
add_op_t tbl_type_ops[n_types][n_types][vt_op_num];
//填充函数指针表单个元素
template<int m, int n, int op>
inline void set_tbl_type() {
typedefmpl::deref<mpl::advance<mpl::begin<op_types>::type, 
mpl::int_<m> >::type>::typetype_1;
typedefmpl::deref<mpl::advance<mpl::begin<op_types>::type, 
mpl::int_<n> >::type>::typetype_2;

tbl_type_ops [m][n][op]=add_op<type_1, type_2, op>;
}

template<int m, int n, int op>
struct fill_tbl_types_op
{
void operator()() {
set_tbl_type<m-1, n-1, op-1>();
fill_tbl_types_op<m, n, op-1>()();//递归
}
};
template<int m, int n>
struct fill_tbl_types_op<m, n, 0>//特化,递归结束
{
void operator()(){}
}

template<int m, int n, int op>
struct fill_tbl_types_n
{
void operator()() {
fill_tbl_types_op<m, n, op>();
fill_tbl_types_n<m, n-1, op>()();//递归
}
};
template<int m, int op>
struct fill_tbl_types_n<m, 0, op>//特化,递归结束
{
void operator()() {}
};

template<int m, int n, int op>
struct fill_tbl_types_m
{
void operator()() {
fill_tbl_types_n<m, n, op>()();
fill_tbl_types_m<m-1, n, op>()();//递归
}
};
template<int n, int op>
struct fill_tbl_types_m<0, n, op>//特化,递归结束
{
void operator()() {}
};

void fill_tbl_op() {
fill_tbl_types_m<n_types, n_types, vt_op_num>()();
}

template<typename RetT, int op>
struct var_oper
{
RetT operator()(const variant& v1, const variant& v2) {
returntbl_type_ops [v1.get_type_code()][v2.get_type_code]
[op](v1, v2).operator RetT();
}
template<int op>
struct var_oper<variant, op>
{
variant operator()(const variant& v1, const variant& v2) {
returntbl_type_ops [v1.get_type_code()][v2.get_type_code]
[op](v1, v2);
}
于是操作符的实现,成了以下形式:
variant operator+(const variant& v1, const variant& v2) {
returnvar_oper<variant, vt_op_add>(v1, v2);
}
bool operator==(const variant& v1, const variant& v2) {
returnvar_oper<bool, vt_op_equal>(v1, v2);
}

如果还觉得复杂,那么可以进一步使用宏做一些包装。
好了,variant的优化基本上完成了。当然还会有一些方面值得我们去进一步地优化,比如可以利用boost的type traits和标准库的limit优化type_ret模板的实现和类型转换操作的实现等等。这里不再赘述。
需要说明的是,整个优化仅仅针对代码,并未考虑性能问题。在优化的过程中,某些手法的使用实际上降低的性能。比如函数指针表存在间接调用,不如直接使用inline函数来的高效。而且,函数指针表要求所有指向的函数必须以相同的类型返回。为了兼容+、-等操作,我使用了值返回。但对于+=等操作符完全可以利用引用返回,以提升性能。如果要解决这种问题,需要用前面提到的模板封装函数指针表的方案,为每一个操作符创建一个函数指针表加以解决。
另一个性能问题主要是在variant与其它类型的操作中,其它类型转换成variant类型然后再计算。比起直接使用目标类型计算慢不少。这个问题也可以利用GP和TMP消除,但代码会复杂不少。
理论上,利用inline和编译器优化,可以消除大部分性能问题。但不是所有的,函数指针表的间接调用,是无论如何也优化不掉的。
此外,我在实现函数指针表的构造算法时,没有使用函数模板,而是使用了函数对象模板(重载operator()的模板)。这是因为函数模板目前不能局部特化,而这里是必须的。另一方面,由于使用了递归,函数模板无法做到inline(),而使用函数对象模板则不会有此限制。表达式fill_tbl_types_m()();最终(优化)编译后的结果会是这样(伪码):
tbl_type_ops [2][2][0]=add_op<string, string, 0>;
tbl_type_ops [2][1][0]=add_op<string, int, 0>;

tbl_type_ops [1][2][0]=add_op<int, string, 0>;

递归和函数对象的调用没有了,完全inline化了。inline函数有时却无法做到这一点。而fill_tbl_types_op等模板实际上起到了代码生成器的作用。这也是GP的一个鲜为人知的功能。如果你有一大堆代码需要编写,而这些代码有很强的规律性和重复性,那么请优先考虑使用模板来为你生成代码,又快又好。
该总结了。如果审视一些代码,会发现只要存在重复和规律性,我们总能利用一些技术和方法加以优化,减少代码量,简化代码结构,减少潜在错误,最终提高开发效率。这里,我使用了C++的泛型编程和模板元编程技术,大幅优化了variant类型中的大量冗余代码。并且为variant类型构建了一个灵活,而又易于扩充的结构。此类技术有很广的应用,不仅仅局限在variant这种底层构件中。相关的一个应用就是构造抽象类工厂,在《Modren C++ Design》一书中,有很完整的案例。
此外,这类技术对于调和运行时多态(OOP)和编译时多态(GP)的矛盾有很大的作用。variant只有在运行时方能确定其具体的类型,而C++的模板只能提供编译时的GP。我利用函数指针数组(当然在更复杂的应用中,可以利用OOP的动多态机制),实现运行时分派操作。而利用GP和TMP大幅简化函数指针数组、操作实现函数,以及操作符的构造。这些技术和方法可以在大多数需要运行时多态,但又存在大量重复或雷同代码的地方得以应用。

--------------------------------------------------------

sf

--------------------------------------------------------

传说中的sf被楼主自己占用了。

非常佩服楼主对C++还有着如此的热情。大受鼓舞!
不知道lz的blog中是否有副本,那样就不用去CSDN满世界找文章了。

--------------------------------------------------------

这些帖子里只是一些框架性的内容,还有很多细节的东西需要补充。过些日子,我会把这些内容整合起来,放到blog里。

--------------------------------------------------------

C++,有些东西被搞得太聪明了。
太喜欢在没有语法直接支持的情况下实现“先进”功能了。

--------------------------------------------------------

我在工作当中不用variant等新奇的东西

--------------------------------------------------------

同意楼上,variant绝不是什么好东西。
无论用什么方式实现、无论编译器是否支持,它都要额外消耗处理器和内存资源。

并且,使用它,还将导致程序行为变得古怪,增加排错的难度。

所以,能用简单类型,就用简单类型。除非是要与com打交道、因而必须兼容对方接口这样的特殊情况,否则不应在任何场合使用variant。

--------------------------------------------------------

mark

--------------------------------------------------------

LZ很厉害,对模板元编程(TMP)也有研究,佩服佩服。

除了LZ的方法,我提供一种单向的类型转换方案。
我觉得,对含有variant的操作而言,最直观的方法就是将variant对象转换为对应的类型。但是,使用隐式转换函数operator T()有比较大的风险,对已存在的函数库是一种冲击,要考虑的问题很多,举个例子:
如已经有一个重载函数 fun_sample(int)和fun_sample(double),而你需要对variant使用它。则如果同时定义operator int() 和operator double(),就会造成二义性。所以,我觉得除非是在任何情况下永远不会冲突的类型,否则对一个类,最多只定义一个operator T()。
既然隐式转换不合适广泛使用(即使对现在的类型集合适,将来也对扩充不利),因此定义显式的转换函数比较好。
这里定义一组显式的转换函数,它的语义是由外界提出类型转换要求,由variant来实现转换:
template<typename T>
T variant::to_type(T*) 
{
    T t;
    return t; // 默认行为不返回实际值 
}
然后特化每个一已支持的类型。

为了让这个函数的使用更方便,需要做一件事:将T*和对应的vt_ 族关联起来,可以定义函数:
template<typename T>
int variant_to_vt(T*)
{
    return variant::vt_empty;
};
然后为每个支持的vt_做特化,如对vt_int:
template<>
int variant_to_vt(int*)
{
    return variant::vt_int;
};

完成这些以后,就可以为
template<typename T> 
T variant::to_type(T*) 
做合适的特化了。如对int :
template<>
int variant::to_type(int*)
{
    return ival;       
}
注意:这里没有考虑它的类型匹配问题。在面向安全的系统中,可以考虑以下实现:
template<>
int variant::to_type(int* p_i)
{
   if (var_type == variant_to_vt(p_i))
       return ival;
   else
       throw exception("bad type!!"); // 或其他合适的处理方式。
}
也可以在这个函数中应用内部转化策略。如部分string可以转化成int等等。

接下来可以在操作符重载中进行单向转换了:
template<typename T>
T operator+(const variant& v, T t)
{
    return v.to_type(&t) + t; 
}
template<typename T>
T operator+(T t, const variant& v)
{
    return t + v.to_type(&t);
    // 可选的方案,在满足交换率时可用
    // return v + t; 
}
麻烦的是variant operator+(const variant& v1,const variant& v2) 。希望把其中一个v转化成对应类型,再和另一个做+法。但是,在一定条件下,它是有二义性的。比如v1中的是int,v2中的是string,在variant支持int和string的相互转化时,v1 + v2 的语义是什么呢?int是数值加,string是字符串连接,所以就有二义性。需要设定优先策略(如数值优先,或左操作数优先等等)。
在解决二义性后(现在假设是左操作数优先),它的实现也存在一个问题:怎样将v1转化成其vt对应的类型呢?老实说,template技术无法解决这个问题,因为无法将一个非编译时常量作为template参数。
所以,我还是老老实实用switch了
variant operator+(const variant& v1,const variant& v2)
{
    switch(v1.get_type_code())
    {
      case vt_int:
         return variant( v1.to_type((int*)0) + v2 );
         // 当然,有一个variant(int)的构造函数,或其他从int到variant的设置方法
      ...
    }
}
当然,也可以不使用switch,以LZ给出的函数指针数组或仿函数数组来代替(后者用多态),为每个左操作数类型写一个函数(或仿函数类型),完成一个case的功能。方法是类似的,这里不赘述了。

这不能不说令人有些遗憾。其实我最希望的是能够将一个变量对应到一个类型,并且可以随意地使用这个类型定义新的对象(这里需要一个T*的对象)。但是我不知道该怎么做,貌似C++中目前不支持这个特性。
所以类似 operator + (const variant&,const variant&) 这一族的函数在单向转换方案中没有优雅的解,期待有人能研究出好方法。

这里没有用到模板元编程(TMP)等前沿技术。使用它的话,应该能解决类似LZ解决了的问题。不过我还没学会用它,所以这里就不深入下去了。相对于技术而言,我更想表达的其实是一开始说的“将variant对象转换为对应的类型”再做操作,这一解决问题的简单思路。但有时,简单的往往很难实现,呵呵。

--------------------------------------------------------

又有的学了,呵呵~~

--------------------------------------------------------

variant只是一个幌子。选用variant因为它比较典型,具有代表性。而且相关的运算比较简单,易于抽象。但本帖的核心思想有两个:利用GP和TMP优化代码,以及弥合运行时多态和编译时多态的矛盾。至于variant的是是非非,不是这里关心的。我在开发中除非迫不得已,比如使用ms的com版的xml解析器,一般是不会用variant的。
在这里,TMP是一个关键。mpl::vector作为类型的容器,使得我们可以把类型操作也能够抽象化,建立一个抽象的系统。这样一个系统可以使我们在需求扩充时,如增加一个类型,只需定义类型及其相关的内容,而针对类型的操作已经无需做任何变化。就像STL中的容器和算法,无论所接受的对象如何变化,容器和算法的结构无需任何改动。
其次,模板为我们提供了面向一类,而不是一个问题编程的能力。而特化使得我们可以应对这“一类问题”中的一些特殊情况。这便是GP的威力。值得注意的是,泛化和特化是GP的核心支柱,两者缺一不可。否则,GP将是不完全的。Java、C#的语言,尽管提供了泛型,但由于缺少了特化,除了制作通用容器外,没有任何其它的用处,GP的威力至少折损掉8成。
编程的灵魂在于抽象,不然,人们也无需费尽心机发展如此众多的编程技术和方法。如果能够进行良好的抽象,那么我们可以化更少的时间,完成更多的工作。而且能够以最简洁、最优雅的形式体现出我们的劳动成果。
很多人不理解,为什么我总是喜欢用代码量来衡量某种实现的好坏。实际上,在我的经验中,代码越简洁,越具有抽象性和逻辑性,出错的可能性越小。俗话说,多个香炉多个鬼,代码越多、冗余越多,可能出错的地方也越多。如果可以用一个抽象的算法替代众多具体的算法,那么我们所需编写、调试和测试的算法只有一个。如果这一个正确了,那么所有使用的算法也是正确的。但是面对一堆具体算法,我们将不得不对这些算法逐个编写、调试和测试。每个环节都有可能出错。人在厌烦和疲劳的时候,什么错都会犯的。
此外,寻找抽象的解决方案时,我们必然会抽取一类问题的共同点,忽略不同点。这样,当我们按照抽象後的共同点建立软件模型後,这个模型将是已与扩展的。因为共同点已经固化,我们在扩展时所需关心的仅仅是那些不同的地方。
编程语言和技术发展的过程,实际上也是不断强化抽象手段的过程。从最初的非结构化转向结构化编程,程序员获得了第一个抽象的手段。数据抽象的出现,提供了对数据及其行为的抽象能力。面向对象为我们提供了对一类抽象数据的描述和扩展的方法。而泛型编程,则使我们可以建立类型无关的系统。最后,元编程的出现,使得类型可以作为计算的对象,建立面向类型的抽象计算系统。
所有这一切,无不体现了抽象的重要。就如同数学在最初仅仅是对数的计算,发展到对抽象的变量的计算,在发展到对函数的计算,“函数的函数”(泛函)的计算。以至于最后出现了“描述数学的数学”(抽象代数)。
C++可以说是编程抽象手段的博物馆,掌握C++的各种技术,也就掌握了各种基本的抽象方法。这也是成为一个成熟的程序员,乃至编程高手的金钥匙。

--------------------------------------------------------

最近C版好贴频出,这就是其中一篇,
向楼主致敬!

--------------------------------------------------------

至于TMP,使用起来也没那么深奥(当然,实现起来是颇费脑力的)。boost::mpl做得非常好。它不象loki那样特立独行,而是将STL中的诸多概念照搬过来,形成了同STL类似的体系。所不同的仅仅是STL计算的是数据对象,而boost::mpl计算的是类型。
所以,mpl的容器也有容器,包括mpl::vector,mpl::deque,mpl::list,mpl::map等等,命名上几乎和STL一样。mpl也有迭代器,有一些算法负责操作迭代器,如我帖子里用到的mpl::advance,语义同STL的advance一样。当然,mpl也有算法,如mpl::count,mpl::find_if等等。此外,还有inserter之类的辅助类型和算法。
mpl中提出了“元函数”的概念。一般的函数通常是:
double f(int, double);
而元函数,实际上是一类特殊的类模板:
template<typename T>
struct f       //用class也可以,但struct方便些,无需public:
{
    typedef T* ptr_type;   //通常只有typedef或static const等编译时内容,没有的成员
    typedef T& ptr_type;
}
元函数用起来差不多是这样:
typedef f<int>::type ptr_int; //ptr_int就是int*
而普通函数用起来:
double a=f(10, 2.3);
两者颇为相似。元函数的typedef相当于普通函数的赋值(=)。而元函数内的public typedef可以看作“返回值”。(请注意,元函数可以有不止一个“返回值”)。元函数没有循环,但可以递归。(所以,TMP实际上可以看作一种函数式编程语言)。而分支语言则使用模板的特化实现。
一旦习惯了元函数那种古怪的风格,那么使用mpl则是一件轻而易举的事,特别是对于熟悉STL的人:
typedef mpl::vector<int, double, string, bool> my_types;
typedef find<my_types, double>::type db_type;  //算法的返回::type是一个iterator
typedef begin<my_types>::type first;  //mpl的容器不可能有成员,所以begin是独立元函数
const db_id=distance<first, db_types>::value;  //获得double在my_type中的序号,是1
总的来说,实现mpl是恐怖的事。但使用起来还是惬意的。:)

抱歉!评论已关闭.