boost 的函数式编程库 Phoenix入门学习

系统 1756 0

这篇文章是我学习boost phoenix的总结。


序言

Phoenix是一个C++的函数式编程(function programming)库。Phoenix的函数式编程是构建在函数对象上的。因此,了解Phoenix,必须先从它的基础函数对象上做起。

Phoenix能够提供令人惊艳的编码效果。我先撂一个出来,看看用Phoenix能写出什么样的代码:

 

        std::for_each(vec.begin(), vec.end(),

        if_(arg1 > 5)

        [

            std::cout << arg1 << ">5\n"

        ]

        .else_

        [

            if_(arg1 == 5)

            [

                std::cout << arg1 << "== 5\n"

            ]

            .else_

            [

                std::cout << arg1 << "< 5\n"

            ]

        ]

    );


  

这是C++代码?答案是肯定的!只需要C++编译器,不需要任何额外的工具,就能实现这样的效果。这是怎么回事?且看下面逐步分解。

 


在此之前,编译phoenix库必须

包含核心头文件

 

    #include <boost/phoenix/core.hpp>
  


注意,不要使用using namespace boost::phoenix,而要直接使用using boost::phoenix::val, ....,如

 

 

    using boost::phoenix::val;

using boost::phoenix::arg_names::arg1;

using boost::phoenix::arg_names::arg2;

using boost::phoenix::case_;

using boost::phoenix::ref;

using boost::phoenix::for_;

using boost::phoenix::let;

using boost::phoenix::lambda;

using boost::phoenix::local_names::_a;


  


为什么不要直接使用using namespace boost::phoenix呢?因为这样会带来不可预知的问题。这是我在实践中发现的。boost的宏BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY以及类似的宏,会出现编译错误。真实的原因是什么,没有细致考究。

 

另外一个原因是,要防止不必要的命名污染,因为phoenix用了很多和boost库冲突的名称,这些在使用的时候,很容易造成问题。


基础函数对象

values

包含头文件:

 

    #include <boost/phoenix/core.hpp>
  

使用命名空间:

 

 

    using boost::phoenix::val;
  

例子

 

 

    val(3)

val("Hello, World")


  

val(3) 生成一个包含整数3的 函数对象 。val("Hello, World")则是一个包含字符串的 函数对象

 

他们是函数对象,因此,你可以象函数那样调用他们

 

    std::cout << val(3)() << val("Hello World")()<<std::endl;


  

val(3)() 将返回值3, val("Hello World")() 将返回值"Hello World"。

 

也许,你会觉得,这简直是多此一举。但是,事实上,你没有明白phoenix的真正用以。

val(3)和val("Hello World") 实际上实现了一个懒惰计算的功能,将对3和"Hello World"的求值,放在需要的时候。


上面的表达式,还可以写成这样

 

    (std::cout << val(3) << val("Hello World")<<std::endl)();
  

括号中std::cout << .. 这一长串,实际上生成了一个函数对象,因此我们才能在需要的时候,调用这个函数对象。

 


这是val的真正威力,它让求值推迟到需要的时候。在普通编程中,我们必须通过类和接口才能完成。


 

References

包含头文件

 

 

    #include <boost/phoenix/core.hpp>
  

使用命名空间

 

 

    using boost::phoenix::ref;


  

如果声明了如下变量:

 

 

        int i = 3;

    char const* s = "Hello World";

    std::cout << (++ref(i))() << std::endl;

    std::cout << ref(s)() << std::endl;
  


ref与val都是可以延迟求值的,但是,不同的是,ref相当于 int& 和 char const*& 的调用。

 

因此,上面 (++ref(i))()的返回值是4,而且,变量i的值也将变为4.

references是phoenix的函数对象和外部变量交换数据的桥梁。

 

Arguments

还记得boost中有_1, _2, _3, ...这些东西吗?在phoenix中有一种类似的 arg1, arg2, arg3, ...。他们有相似的作用,但是arg1 事实上是函数对象。

 

包含头文件

 

    #include <boost/phoenix/core.hpp>
  

 

使用命名空间

 

    using boost::phoenix::arg_names::arg1;

using boost::phoenix::arg_names::arg2;

using boost::phoenix::arg_names::arg3;

....


  

看下面的例子

 

        std::cout << arg1(3) << std::endl;

    std::cout << arg2(2, "hello world") << std::endl;
  


输出的结果是 3, "Hello world"。 

 

 

  • arg1接收1个以上的参数,然后返回第1个参数
  • arg2接受2个以上的参数,然后返回第2个参数
  • arg3接受3个以上的参数,然后返回第3个参数

 

依次类推。


那么,这样的东西有什么用呢?它实际上是用来提取参数的。arg1提取第一个参数,arg2提取第二个参数,....

比如,我们有一个函数

 

    void testArg(F f)

{

    f(1,2,3);

}



...

int main()

{

    testArg(std::cout<<arg1<<"-"<<arg2<<"-"<<arg3<<std::endl);

}
  

std::cout ... 这一长串生成了一个函数对象。arg1 ,arg2, arg3分别提取了testArg传递的参数1,2,3。因此,这个函数会返回"1-2-3"。如果你将arg1和arg3的位置兑换下,返回的结果将是"3-2-1"。

 


 

Lazy Operators

操作符也可以生成函数对象。

 

头文件

 

    #include <boost/phoenix/operator.hpp>
  


无需命名空间

 

看个例子

 

    std::find_if(vec.begin(), vec.end(), arg1 %2 == 1);
  


find_if的功能是查找第一个符合条件的对象,然后返回。它要求最后一个参数为一个函数或者函数对象。那么 arg1 %2 == 1是一个函数对象吗?

 

答案是肯定的!。

它一共涉及两个操作符 %和 == 。 arg1 % 2 生成一个函数对象,新生成的函数对象在通过 == 操作符,又生成了新的对象。

它实际上就是

 

    auto func1 = operator % (arg1, 2);

auto func2 = operator == (func1, 1);
  

最后的func2被传递给了find_if。

 


phoenix支持所有的操作符,包括一元操作符在内,

如:

 

    1 << 3;      // Immediately evaluated

val(1) << 3; // Lazily evaluated
  


支持的单目运算符有

 

 

    
      prefix
    
    
      :
    
    
      ~,
    
    
      !,
    
    
      -,
    
    
      +,
    
    
      ++,
    
    
      --,
    
    
      &
    
    
      (
    
    
      reference
    
    
      ),
    
    
      *
    
    
      (
    
    
      dereference
    
    
      )
    
    
      postfix
    
    
      :
    
    
      ++,
    
    
      --
    
  

 


支持的双目运算符有

 

    
      =,
    
    
      [],
    
    
      +=,
    
    
      -=,
    
    
      *=,
    
    
      /=,
    
    
      %=,
    
    
      &=,
    
    
      |=,
    
    
      ^=,
    
    
      <<=,
    
    
      >>=
    
    
      +,
    
    
      -,
    
    
      *,
    
    
      /,
    
    
      %,
    
    
      &,
    
    
      |,
    
    
      ^,
    
    
      <<,
    
    
      >>
    
    
      ==,
    
    
      !=,
    
    
      <,
    
    
      >,
    
    
      <=,
    
    
      >=
    
    
      &&,
    
    
      ||,
    
    
      ->*
    
  

 


三目运算符

 

    
      if_else
    
    
      (
    
    
      c
    
    
      ,
    
    
      a
    
    
      ,
    
    
      b
    
    
      )
    
  

 


支持成员函数指针操作

 

    
      struct
    
    
      A
    
    
      {
    
    
      int
    
    
      member
    
    
      ;
    
    
      };
    
    
      A
    
    
      *
    
    
      a
    
    
      =
    
    
      new
    
    
      A
    
    
      ;
    
    
      ...
    
    
      (
    
    
      arg1
    
    
      ->*&
    
    
      A
    
    
      ::
    
    
      member
    
    
      )(
    
    
      a
    
    
      );
    
    
      // returns member a->member
    
  

arg1->*&A::member实现一个对A对象的访问操作。

 


 

Lazy Statements

懒惰语句。

 

头文件

 

    #include <boost/phoenix/statement.hpp>
  


命名空间

 

 

    using boost::phoenix::if_;

using boost::phoenix::switch_;

using boost::phoenix::case_;

using boost::phoenix::while_;

using boost::phoenix::for_;

....
  


我们看看if_的例子

 

 

        std::for_each(vec.begin(), vec.end(),

        if_(arg1 > 5)

        [

            std::cout << arg1 << ","

        ]

        );


  


虽然看起来很奇怪,但是,它的确是C++的语法。这个里面也充斥了函数对象。我们可以这样看

 

 

    if_.operator()( 

     operator > (arg1, 5)

 ) .operator[](

    operator<<(

               operator<<(std::cout, arg1)

             , ",")

)
  

 


Lazy Statement还有很多类似的语法。它的目的是为了模拟C++的语法。 用C++模拟C++


它用逗号代替分号,模拟语句序列,如

 

    
      statement
    
    
      ,
    
    
      statement
    
    
      ,
    
    
      ....
    
    
      statement
    
  

主要,最后一条"语句"(实际上是函数对象),不能有“,”,如是这样

 

 

    
      statement
    
    
      ,
    
    
      statement
    
    
      ,
    
    
      statement
    
    
      ,
    
    
      // ERROR!
    
  

就错了。

 

可以用括号来扩住一些语句(即函数对象)

 

    
      statement
    
    
      ,
    
    
      statement
    
    
      ,
    
    
      (
    
    
      statement
    
    
      ,
    
    
      statement
    
    
      ),
    
    
      statement
    
  

括号也可以用在最外层,将语句(即函数对象)进行分组,如

 

 

    
      std
    
    
      ::
    
    
      for_each
    
    
      (
    
    
      c
    
    
      .
    
    
      begin
    
    
      (),
    
    
      c
    
    
      .
    
    
      end
    
    
      (),
    
    
      (
    
    
      do_this
    
    
      (
    
    
      arg1
    
    
      ),
    
    
      do_that
    
    
      (
    
    
      arg1
    
    
      )
    
    
      )
    
    
      );
    
  

 


 

Construct, New, Delete, Casts

可以重载类的这些实现:

 

 

    
      construct
    
    
      <
    
    
      std
    
    
      ::
    
    
      string
    
    
      >(
    
    
      arg1
    
    
      ,
    
    
      arg2
    
    
      )
    
    
      // constructs a std::string from arg1, arg2
    
    
      new_
    
    
      <
    
    
      std
    
    
      ::
    
    
      string
    
    
      >(
    
    
      arg1
    
    
      ,
    
    
      arg2
    
    
      )
    
    
      // makes a new std::string from arg1, arg2
    
    
      delete_
    
    
      (
    
    
      arg1
    
    
      )
    
    
      // deletes arg1 (assumed to be a pointer)
    
    
      static_cast_
    
    
      <
    
    
      int
    
    
      *>(
    
    
      arg1
    
    
      )
    
    
      // static_cast's arg1 to an int*
    
  

 

 

函数适配器

头文件

 

    #include <boost/phoenix/function.hpp>
  


命名空间

 

 

    boost::phoenix::function
  

 

 

函数对象包装

 

考虑一个factorial函数

 

    struct factorial_impl

{

    template <typename Sig>

    struct result;

    

    template <typename This, typename Arg>

    struct result<This(Arg)>

        : result<This(Arg const &)>

    {};



    template <typename This, typename Arg>

    struct result<This(Arg &)>

    {

        typedef Arg type;

    };



    template <typename Arg>

    Arg operator()(Arg n) const

    {

        return (n <= 0) ? 1 : n * this->operator()(n-1);

    }

};


  


解析一个这个实现:

 

factorial_impl的result声明是必须的,这是phoenix的模板要求的。result的声明使用了半实例化模板

 

        template <typename Sig>

    struct result;
  

这是声明一个主模板,当然,主模板没有任何用处,因此只声明不定义。

 

 

        template <typename This, typename Arg>

    struct result<This(Arg &)>

    {

        typedef Arg type;

    };
  

这是一个半实例化的模板。从 result<This(Arg&)>可以看出。 This(Arg&)声明一个返回对象为 This, 参数为Arg& 的函数。

 

后面,Arg operator()(Arg)就是函数对象的实现体了。


使用时,需要这样

 

    int

main()

{

    using boost::phoenix::arg_names::arg1;

    boost::phoenix::function<factorial_impl> factorial;

    int i = 4;

    std::cout << factorial(i)() << std::endl;

    std::cout << factorial(arg1)(i) << std::endl;

    return 0;

}
  

 

 

适配函数宏

 

上面的代码,书写起来,还是比较麻烦的,因此,phoniex提供了几个宏,用于帮助实现函数对象的适配。

针对普通函数的宏

 

BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY BOOST_PHOENIX_ADAPT_FUNCTION

 

它的语法是

 

      
        BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY
      
      
        (
      
      
        RETURN_TYPE
      
      
        ,
      
      
        LAZY_FUNCTION
      
      
        ,
      
      
        FUNCTION
      
      
        )
      
    
      
        BOOST_PHOENIX_ADAPT_FUNCTION
      
      
        (
      
      
        RETURN_TYPE
      
      
        ,
      
      
        LAZY_FUNCTION
      
      
        ,
      
      
        FUNCTION
      
      
        ,
      
      
        FUNCTION_ARITY
      
      
        )
      
    

NULLARY表明是没有参数的。

 

针对NULLARY的例子:

声明函数:

 

      namespace demo

{

    int foo()

    {   

        return 42; 

    }   

}
    

生成函数对象

 

 

      BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY(int, foo, demo::foo)


    

使用它:

 

 

      std::cout << "foo()():"<<foo()() << std::endl;


    

foo() 返回一个函数对象。 foo是一个函数,你可以认为是函数对象的工厂。

带参数的例子

 

 

      namespace demo

{

    int plus(int a, int b)

    {   

        return a+b;

    }   



    template<typename T>

    T plus ( T a, T b, T c)

    {   

        return a + b + c;

    }   

}



BOOST_PHOENIX_ADAPT_FUNCTION(int, myplus, demo::plus, 2)



BOOST_PHOENIX_ADAPT_FUNCTION(

    typename boost::remove_reference<A0>::type

    , myplus

    , demo::plus

    , 3 

    )   


    


这样使用

 

 

          int a = 123;

    int b = 256;

    std::cout<<"myplus:"<<(myplus(arg1, arg2)(a, b)) << std::endl;

    std::cout<<"myplus<3>:"<<(myplus(arg1, arg2, 3)(a, b)) << std::endl;


    


myplus(arg1, arg2, 3) 生成一个函数对象,这个函数对象接收两个整数参数。

 


至于细节,了解不是很多,不管怎么样,用就是了。


针对函数对象的宏

 

BOOST_PHOENIX_ADAPT_CALLABLE_NULLARY BOOST_PHOENIX_ADAPT_CALLABLE

 

在使用上,同FUNCTION对应的函数,但是,它是针对函数对象的。

 

      namespace demo

{

    struct foo2 {

        typedef int result_type;

        int operator()() const

        {

            return 42;

        }

    };

}



BOOST_PHOENIX_ADAPT_CALLABLE_NULLARY(foo2, demo::foo2)
    


声明方法和BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY 几乎是一样的,但是它不需要给出返回值。

 

值得注意的是,foo2中 typedef int result_type; 的声明是必须的,因为,它是phonix模板要求的,一旦没有,就会出错。


带有重载的例子

 

      namespace demo

{

    struct plus

    {

        template<typename Sig>

        struct result;



        template<typename This, typename A0, typename A1>

        struct result<This(A0, A1)>

            :boost::remove_reference<A0>

            {};

        template<typename This, typename A0, typename A1, typename A2>

        struct result<This(A0, A1,A2)>

            :boost::remove_reference<A0>

            {};



        template<typename A0, typename A1>

        A0 operator()(A0 const& a0, A1 const &a1) const

        {

            return a0 + a1;

        }

        template<typename A0, typename A1, typename A2>

        A0 operator()(A0 const& a0, A1 const &a1, A2 const &a2) const

        {

            return a0 + a1 + a2;

        }

    };

}



BOOST_PHOENIX_ADAPT_CALLABLE(plus, demo::plus, 2)

BOOST_PHOENIX_ADAPT_CALLABLE(plus, demo::plus, 3)


    

struct result的声明也是使用了半实例化的技巧。需要给出参数个数,这个是很重要的。

 

语句

语句在上面提到过,这里介绍更多的语句

 

if_else_ 语句

 

我们开头看到的,就是一个if_else_语句

 

          std::for_each(vec.begin(), vec.end(),

        if_(arg1 > 5)

        [

            std::cout << arg1 << ">5\n"

        ]

        .else_

        [

            if_(arg1 == 5)

            [

                std::cout << arg1 << "== 5\n"

            ]

            .else_

            [

                std::cout << arg1 << "< 5\n"

            ]

        ]

    );
    

 

if_最终生成了一个函数对象,它还有一个.else_对象,这个对象也是一个函数对象,可以接收任何函数对象。于是,这样就被层层包含起来,形成了上面的奇观。

 

switch_ 语句

看这个例子
            std::for_each(vec.begin(), vec.end(),

        switch_(arg1)

        [

            case_<1>(std::cout<<arg1<<":"<<val("one") << "\n"),

            case_<2>(std::cout<<arg1<<":"<<val("two") << "\n"),

            default_(std::cout<<arg1<<":"<<val("other value") << "\n")

        ]

    );


      
注意default_后面是不加","的。
case_和default_都是函数对象。

while_ 语句

例子:
              int value;



    std::for_each(vec.begin(), vec.end(),

        (

            ref(value) = arg1,

            while_(ref(value)--)

            [

                std::cout<<ref(value)<<","

            ],

            std::cout << val("\n")

        )

    );


        
我用了ref(value)来作为临时变量。
这样的代码写起来,和对应的C++代码很相似。但是它实际上是一堆函数对象的组合。它是延迟加载的,这一点很重要。

与之相似的还有do_while_循环。


for_ 语句

            int iii;

    std::for_each(vec.begin(), vec.end(),

        (

            for_(ref(iii) = 0, ref(iii) < arg1, ++ ref(iii))

            [

                std::cout << arg1 << ", "

            ],

            std::cout << val("\n")

        )

    );


      

无语了。

其他语句

另外还有,try_catch_和throw_语句,实现原理都差不多。

 

总结

以上的介绍是浅尝辄止,phoenix还有很多高级的东西未曾涉及,有兴趣的读者可以看boost相关内容。

phoenix让我重新认识了C++的模板。C++的模板是C++元编程的重要利器。它甚至一定程度上改变了C++语言的语法。


不过,个人觉得,phoenix也有些过度设计。其实,语句部分,可以通过编写专门的函数来实现。这对大多数人来说,也就是多敲几行代码的问题。

我觉得有价值的是phoenix对函数的包装,使得C++的函数具备了懒计算的能力。

懒计算避免了我们定义N多接口,以及和N多接口配合的N^N的类工厂和派生类。

使用FP编程,不必像OO编程那样,设计者为了保证接口的兼容性,绞尽脑汁的设计接口;使用者不必为了实现一个简单的功能,派生一大堆类,和一大堆工厂。

设计者根据需要,要求传递函数对象即可;使用者只需要包装一个自己的实现给它使用,一切都搞定了。




 

boost 的函数式编程库 Phoenix入门学习


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论