元函数or_之我实现
作者:林永听
网址:http://blog.csdn.net/linyt/archive/2009/01/08/3730610.aspx
元函数(matefunction)or_是Boost.MPL库中极重要的元素,它提供了对类型的“或”运算。它与运行时的||操作符相常似类,具有短路求值的性质。Boost官方文档上对该元函数的接口和语义有详细的说明。下面就它的语义作简要表述:
与boost.MPL元函数接口一致,它接受多个类型实参,唯一一个返回值为它内嵌的type类型。
Typedef or_<f1, f2, ..., fn>::type r;
若f1::type::value,或f2::type::value, ...,或fn::type::value表达式值为true, 则r为true_, 否则为false_。
请注意,or_即有短路求值的性质,若fk::type::value值为true(k = min{ki|fki::type::value == true}),则不对fj::type(j>k)进行求值。
因此,下面的代码可正常编译:
struct unknow{};
BOOST_MPL_ASSERT(( or_< true_,unknown > ));
虽然类型unknow没有内嵌的type类型定义,但由or_具有短路求值的性质,它遇到true_时,就可以决定整个or_元函数的结果,它不需要对unknow类型进行求值。
显然下面代码不可能通过编译,因为or_需要对unknow类型进行求值,而它却没有::type::value定义。
struct unknow{};
BOOST_MPL_ASSERT(( or_< false_,unknown > ));
1. 实现要求
为了不与Boost.MPL中的元函数冲突,我把实现之函数命名为logic_or, 并且最少提供两个模板实参,最多不能超过5个,同时实现短路求值的功能。
下面是该实现必须通过的测试代码:
2. 笨拙的实现
显然logic_or可用于对任意多个类型进行求值(2到5个),那么它需要一些默认的模板形参,很容易知道它的声明式类似如下:
这样,用户只需把他关注的类型填进该函数里面进行计算,而不需要每个参数都指定,这就是默认形参的作用,与运行时函数定义一致。用户即可如下使用
实现||这运算符的语义非常容易,相信大家都会有下面这样的实现想法:
显然利用编译器编译时所支持的||运行算,不费吹灰之力就可实现or_的语义,不信,可以测试一下:
的确,上面的程序可以通过测试。但别开心得太早,上面四行测试代码并没有涉及到||操作符的短路求值特性。既然logic_or为Boost库中or_的实现,那它应该与运行时的||运算符一致。
上述方案中,编译器在表达式L1::type::value || L2::type::value|| L3::type::value|| L4::type::value|| L5::type::value 求值,时即使有短路求值的保证,那也只表示,当L1::type::value值为true时,对L2::type::value以及后面的value不进行计算,但并不表示编译器不对L2::type::value进行类型展开。Boost中or_元函数的短路求值语义,可以两行代码敝之:
Struct unknown{};
BOOST_STATIC_ASSERT(( or_<true_, unknown> ));
若把这两行测试加于上方案,编译器会抱怨unknown类型没有内嵌的type定义。显然编译时的计算通常指对常量以及类型的计算,对上面的L2来说,编译器分析L2::type时就是对L2的编译时计算,因为编译器要依据现要的信息来“计算”它是否有内嵌的type子类型,或常量,以及它的类型。因此logic_or的短路求值,就是指若L1::type::value值为true时,不需要对L2::type以及L2::type::value计算,即L2是一个类型就可以了,不必理会它是否有内嵌的type::value。
现在留下的悬念莫过于如何实现短路求值。其实心里不时念着:
如L1::type::value值为true,则结果为true_, 否则,
如L2::type::value值为true, 则结果为true_, 否则,
……
如L5::type::value值为true, 则结果为true_, 否则,
为false_
由于短路求值的要求,在未知L1::type::value为false时,绝不能对L2进行任何的求值,如L2::type::value。在实现方面,类logic_or的形参为L1, L2, L3,等,但要对L1::type::value值进行特化,即如下:
代码中Tn(Tn::type::value == true) 表达特化的版本,要求当对Tn类型满足条件Tn::type::value == true时,采用该特化版本进行实例化。显然这样的要求是无理的,C++标准根本不支持这样的特化,估计将也不会,因为这是假想出来的一种特化形式。既然特化只能,而且也只能对类型进行特化,而不能对它内嵌的类型或常量进行特化,那么我们只能照样画葫芦,对L1::type::value,L2…L5::type::value进行化,而非L1,L2,L3….L5特化。
假设C++模板功能非常聪明,明眼就看到L1::type::value,…, L4::type::value的值为flase,那么关键性的任务落到L5::type::value的肩上。
可以写简单的代码来实现:
logic_or_impl_5<L5::type::value>就是我们想要的结果了,遗憾的是,前面只是假设,否则就大功告成了。毕竟编译器没有这么明智,它需要我们的引路,现在来完成这个“假说”吧。不骗你,接下来仍然需要假设。正如假设儿子的存在,必须假设父亲的存在,爷爷的存在,……老祖宗的存在,最后只要老祖宗存在了,那么儿子的存在就顺理成章。
L4::type::value值真的就是false?编译一眼就有看出L1::type::value,…, L4::type::value均为true?那可未必,这需要我们实现这个假设。我们仍需要假设L1::type::value,…,L3::type::value被明智的编译器一眼所识别为false,那接下来要对L4进行计算。实现如下:
咦,如果L4::type::value值为false怎么办?这要看T5了,用一个特化模板来依赖T5进行计算(它的计算已在 logic_or_impl_5实现了)。
怎么样,如果T4::type::value值为true时,结果为true_,根本不需要对L5进行计算,只能为false时,才需要计算L5,神奇吧!
……
接下来,大家都清楚如何做了,在此就列出logic_or_impl_1的实现:
经过几个回合后,logic_or实现如下:
至此,上面的测试全部通过,拦路虎终于除去。想想该方案实质上使用了类似递归的方法来实,从impl_1到impl_5一路走来,只是类名和模板参数不同而已,语义上实际是相通的。如果要求模板参数超过5个,多达到30,如果采用这方案,肯定是不可接受的。接下来采用递归来实现logic_or元函数。
3. 递归之美
无论是程序设计方面,还是在计算机代数模型里面,递归都是一种美。尤其在函数式编程里面,递归更是不可少的工具。由于没有变量(只有常量),for/while这一类的循环只能用递归实现。C++模板提供等介的递归能力。
其实大名鼎鼎的LIOK库已定义一个很优美的递归的类型列表,功能是等价于运行时的单向线性表,我们仿此来设计一个,然后用模板的递归能功来遍历该历表。
正如线性一样,类型列线节点(类型)需要两个常量,它的定义如下:
别忘了需要一个哨兵来标记该表的结束。
Struct nullType{};
<T1,T2,T3>这样的类型列表可以如下表示:
typeList< T1, typeList<T2, typeList<T3, nullType> > >
很明显这是递归的结构,正好适用于我们的模板。到此,算法非常简单:
typeList< T1, typeList<T2, typeList<T3, nullType> > >
typeList<T2, typeList<T3, nullType> >
typeList<T3, nullType>
都是一个类型表,即具有 typeList<X, Y>这样的形式,因此对它他的处理方法是一致的,用一个函数就可实现:
Logic_or_list 用于处理类型列表的元函数,如果列表头的类型的::type::value值为true时,结果为true_,即上面代码所定义的。如果为false则用尾递归遍历此表,直到nullType节点为止,因此,需要下面的特化:
Logic_or实现如下:
4.重用先行
重新审视上面的“如果……否则, 如果…否则……”那想法,会发现使用Boost的类型选择元函数就可以实现logic_or的功能,而不需要我们重新发明车轮。
If_c<true, T1, T2>::type 为T1
If_c<false, T1, T2>::type 为T2
利用这一特点,很快就可以实现logic_or的短路求值语义。
把这几行代码放到logic_or类体内就可以了!上述代码仍旧有短路求值的特点,如果某个Ln::type::value值为true,那么后面的tempn到type都为true_,不需要对 Ln+1到L5进行计算。这几行代码在效率上不如上述递归方法高效,无论有没有true,或是哪一个,它都要计算5次才知道知结果。语义有短路求值,但计算量上却没有。还可以提高效率,一想之下就出现下面的代码,使用if_而非if_c。
细想一下,发现此代码没能实现短路求值功能,原因是:实例化最外层if_c时,它先要实例化较内层的if_c,这样If_<T5, true_, false>就是最先实例化的if_类模板实例。依次,T5::type::value, T4,……T1::type::value都要计算。
5. 相关或进一步阅读的资料
1. BOOST_STATIC_ASSERT和 BOOST_STATIC_ASSERT_NOT为 Boost.MPL库里面的编译器断言,可通过http://www.boost.org/doc/libs/1_37_0/libs/mpl/doc/refmanual/asserts.html 来获得详细的描述。
2. if_c, if_, bool_,true_及false_均为Boost.MPL库是元函数,if_c和if_为类型选择元函数,可通过http://www.boost.org/doc/libs/1_37_0/libs/mpl/doc/refmanual/type-selection.html获得详细描述;而bool_, true_和false_为布尔类型整数常量,可通过http://www.boost.org/doc/libs/1_37_0/libs/mpl/doc/refmanual/bool.html获得详细描述。
3. Boost.MPL 是Boost项目是为C++模板元编程写的程序库,它包含了相当丰富的元函数,元函数类,以及容器,迭代器和算法;可谓应有尽有,可通过http://www.boost.org/doc/libs/1_37_0/libs/mpl/doc/index.html来获详细描述。
4. C++模板元编程经典之作:C++ Template Metaprogramming, 作者:David Abrahams,Aleksey Gurtovoy;ISBN 0321227255。