元函数or_之我实现

元函数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个,同时实现短路求值的功能。

   下面是该实现必须通过的测试代码:

  1. #include "logic_or.hpp"
  2. #include <boost/type_traits/is_reference.hpp>
  3. #include <boost/mpl/assert.hpp>
  4. void testLogicalOr()
  5. {
  6.      BOOST_MPL_ASSERT_NOT((logic_or<boost::is_reference<int>,
  7.                                         boost::is_reference<char> >));
  8.  
  9.      BOOST_MPL_ASSERT((logic_or<boost::is_reference<int>,
  10.                                   boost::is_reference<char&> >));
  11.  
  12.      BOOST_MPL_ASSERT((logic_or<boost::is_reference<int &>,
  13.                                      boost::is_reference<char> >));
  14.  
  15.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int &>,
  16.                                 boost::is_reference<char&> >));
  17. }

  18. void testLogicalOrThree()
  19. {
  20.      BOOST_MPL_ASSERT_NOT((logic_or< boost::is_reference<int>,
  21.                                      boost::is_reference<char>,
  22.                                      boost::is_reference<long>
  23.                           >));

  24.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int>,
  25.                                  boost::is_reference<char>,
  26.                                  boost::is_reference<long&> >));

  27.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int>,
  28.                                  boost::is_reference<char&>,
  29.                                  boost::is_reference<long> >));

  30.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int>,
  31.                                  boost::is_reference<char&>,
  32.                                  boost::is_reference<long&> >));

  33.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int&>,
  34.                                  boost::is_reference<char>,
  35.                                  boost::is_reference<long> >));

  36.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int&>,
  37.                                  boost::is_reference<char>,
  38.                                  boost::is_reference<long&> >));

  39.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int&>,
  40.                                  boost::is_reference<char&>,
  41.                                  boost::is_reference<long> >));

  42.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int&>,
  43.                                  boost::is_reference<char&>,
  44.                                  boost::is_reference<long&> >));

  45. }

  46. void testLogicalOrFive()
  47. {
  48.      BOOST_MPL_ASSERT_NOT((logic_or< false_, false_, false_, false_, false_ >));
  49. }

  50. struct dummy;

  51. void testLogicalOrShortCurit()

  52. {
  53.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int&>, dummy>));
  54.      BOOST_MPL_ASSERT((logic_or< boost::is_reference<int>,
  55.                                   boost::is_reference<char &>, dummy >));
  56. }

  57. int main()
  58. {
  59.      testLogicalOr();
  60.      testLogicalOrThree();
  61.      testLogicalOrShortCurit();
  62.      testLogicalOrFive();
  63.      return 0;
  64. }
 

2. 笨拙的实现

  

   显然logic_or可用于对任意多个类型进行求值(2到5个),那么它需要一些默认的模板形参,很容易知道它的声明式类似如下:

 

  1. template <class L1, class L2, class L3 = false_, class L4 = false_, class L5 = flase_>
  2. struct logic_or;

 

   这样,用户只需把他注的类型填进该函数里面进行计算,而不需要每个参数都指定,这就是默认形参的作用,与运行时函数定义一致。用户即可如下使用

  1. typedef logic_or< is_array<T>, is_reference<T> >::type arr_or_ref_tag;

   实现||这运算符的语义非常容易,相信大家都会有下面这样的实现想法:

 

  1. template <class L1, class L2, class L3 = false_, class L4 = false_, class L5 = false_>
  2. Struct logic_or
  3. {
  4.   Typedef bool_<  L1::type::value
  5.                || L2::type::value
  6.                || L3::type::value
  7.                || L4::type::value
  8.                || L5::type::value
  9.          > type;
  10. };

 

显然利用编译器编译时所支持的||运行算,不费吹灰之力就可实现or_的语义,不信,可以测试一下:

  1. BOOST_STATIC_ASSERT_NOT(( logic_or< false_, false_ > ));
  2. BOOST_STATIC_ASSERT(( logic_or< false_, true_ >));
  3. BOOST_STATIC_ASSERT(( logic_or< true_, false_ >));
  4. BOOST_STATIC_ASSERT(( logic_or< true_, true_ >));

 

的确,上面的程序可以通过测试。但别开心得太早,上面四行测试代码并没有涉及到||操作符的短路求值特性。既然logic_orBoost库中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进行类型展开。Boostor_元函数的短路求值语义,可以两行代码敝之:

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::valuefalse时,绝不能对L2进行任何的求值,如L2::type::value。在实现方面,类logic_or的形参为L1, L2, L3,等,但要对L1::type::value值进行特化,即如下:

 

  1. Template <class T1, class T2, class T3 = false_, class T4 = false_, class T5 = false_>
  2. Struct logic_or
  3. {
  4.     Typedef flase_ type;
  5. };
  6. Template <class T1, class T2, class T3 = false_, class T4 = false_, class T5 = false_>
  7. Struct logic_or<T1(T1::type::value == true), T2, T3, T4, T5>
  8. {
  9.    Typedef true_ type;
  10. };
  11. Template <class T1, class T2, class T3 = false_, class T4 = false_, class T5 = false_>
  12. Struct logic_or<T1, T2(T2::type::value == true), T3, T4, T5>
  13. {
  14.   Typedef true_ type;
  15. };
  16. Template <class T1, class T2, class T3 = false_, class T4 = false_, class T5 = false_>
  17. Struct logic_or<T1, T2, T3, T4, T5(T5::type::value == true) >
  18. {
  19.    Typedef true_ type;
  20. };

 

代码中Tn(Tn::type::value == true) 表达特化的版本,要求当对Tn类型满足条件Tn::type::value == true时,采用该特化版本进行实例化。显然这样的要求是无理的,C++标准根本不支持这样的特化,估计将也不会,因为这是假想出来的一种特化形式。既然特化只能,而且也只能对类型进行特化,而不能对它内嵌的类型或常量进行特化,那么我们只能照样画葫芦,对L1::type::valueL2L5::type::value进行化,而非L1,L2,L3.L5特化。

 

 假设C++模板功能非常聪明,明眼就看到L1::type::value,…, L4::type::value的值为flase,那么关键性的任务落到L5::type::value的肩上。

 

可以写简单的代码来实现:

  1. Template<bool c5>
  2. Logic_or_impl_5
  3. {
  4.    Typedef bool_<c5> type;
  5. };

  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进行计算。实现如下:

 

  1. Template <bool c4, class T5>
  2. Struct logic_or_impl_4
  3. {
  4.   Typedef true_ type;
  5. };

   咦,如果L4::type::value值为false怎么办?这要看T5了,用一个特化模板来依赖T5进行计算(它的计算已在 logic_or_impl_5实现了)。

 

  1. Template <class T5>
  2. Struct logic_or_impl_4<flase, T5>
  3.   : logic_or_impl_4<T4::type::value>
  4. {};

    怎么样,如果T4::type::value值为true时,结果为true_,根本不需要对L5进行计算,只能为false时,才需要计算L5,神奇吧!

 

  ……

  接下来,大家都清楚如何做了,在此就列出logic_or_impl_1的实现:

  1. Template <bool c1, class T2, class T3, class T4, class T5>
  2. Struct logic_or_impl_1
  3. {
  4.   Typedef true_ type;
  5. };
  6. Template <class T2, class T3, class T4, class T5>
  7. Struct logic_or_impl_1<false, T2, T3, T4, T5>
  8.  : logic_or_impl_2<T2::type::value, T3, T4, T5>
  9. {};

 

经过几个回合后,logic_or实现如下:

  1. Template< class T1, class T2, class T3 = false_, class T4 = false_, class T5 = false_>
  2. Struct logic_or
  3.   :logic_or_impl_1<T1::type::value, T2, T3, T4, T5>
  4. {};

至此,上面的测试全部通过,拦路虎终于除去。想想该方案实质上使用了类似递归的方法来实,从impl_1impl_5一路走来,只是类名和模板参数不同而已,语义上实际是相通的。如果要求模板参数超过5个,多达到30,如果采用这方案,肯定是不可接受的。接下来采用递归来实现logic_or元函数。

 

 

3. 递归之美

   无论是程序设计方面,还是在计算机代数模型里面,递归都是一种美。尤其在函数式编程里面,递归更是不可少的工具。由于没有变量(只有常量),for/while这一类的循环只能用递归实现。C++模板提供等介的递归能力。

其实大名鼎鼎的LIOK库已定义一个很优美的递归的类型列表,功能是等价于运行时的单向线性表,我们仿此来设计一个,然后用模板的递归能功来遍历该历表。

 

正如线性一样,类型列线节点(类型)需要两个常量,它的定义如下:

  1. Template < class H, class T>
  2. Struct typeList
  3. {
  4.   Typedef H Head;
  5.   Typedef T Tail;
  6. };

 

别忘了需要一个哨兵来标记该表的结束。

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>这样的形式,因此对它他的处理方法是一致的,用一个函数就可实现:

  1. Template < bool HeadValue, class TailList>
  2. Struct logic_or_list:
  3. {
  4.    Typedef true_ type;
  5. };

Logic_or_list 用于处理类型列表的元函数,如果列表头的类型的::type::value值为true时,结果为true_,即上面代码所定义的。如果为false则用尾递归遍历此表,直到nullType节点为止,因此,需要下面的特化:

 


  1. Template < class TailList>
  2. Struct logic_or_list<false, TailList>
  3.   : logic_or_list<TailList::Head::Type::value, typename TailList::Tail>
  4. {
  5. }; 

  6. Template<>
  7. Struct logic_or_list<false, nullType>
  8. {
  9.   Typedef false_ type;
  10. };

Logic_or实现如下:

  1. Template <class T1, class T2, class T3 = false_, class T4 = false_, class T5 = false_>
  2. Struct logic_or
  3. {
  4.    Typedef logic_or_impl_list< T1::type::value,
  5.                               typeList<T2, typeList<T3, typeList<T4,    
  6.                                typeList<T5, nullType> > > > >::type type;
  7. };

 

 

4.重用先行

   重新审视上面的“如果……否则, 如果…否则……”那想法,会发现使用Boost的类型选择元函数就可以实现logic_or的功能,而不需要我们重新发明车轮。

 

  If_c<true, T1, T2>::type T1

  If_c<false, T1, T2>::type T2

 

利用这一特点,很快就可以实现logic_or的短路求值语义。


  1. Typedef if_c<T1::type::value,    true_, T2>::type    temp1;
  2. Typedef if_c<temp1::type::value, true_, T3>::type    temp2;
  3. Typedef if_c<temp2::type::value, true_, T4>::type    temp3;
  4. Typedef if_c<temp3::type::value, true_, T5>::type    temp4;
  5. Typedef if_c<temp4::type::value, true_, false_>::type type;

把这几行代码放到logic_or类体内就可以了!上述代码仍旧有短路求值的特点,如果某个Ln::type::value值为true,那么后面的tempntype都为true_,不需要对 Ln+1L5进行计算。这几行代码在效率上不如上述递归方法高效,无论有没有true,或是哪一个,它都要计算5次才知道知结果。语义有短路求值,但计算量上却没有。还可以提高效率,一想之下就出现下面的代码,使用if_而非if_c

 

  1. Typedef if_<T1, true_,
  2.                 If_<T2, true_,
  3.                         If_<T3, true_,
  4.                                 If_<T4, true_,
  5.                                          If_<T5, true_, false>
  6.                                 >
  7.                         >
  8.                 >
  9.         >::type type;

   细想一下,发现此代码没能实现短路求值功能,原因是:实例化最外层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_cif_为类型选择元函数,可通过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 AbrahamsAleksey GurtovoyISBN 0321227255

 

 

 

      

 

你可能感兴趣的:(编程,c,算法,struct,测试,编译器)