proto 高级篇

context

实现一个 expression 的 evaluation 的过程需要一个具体的 context,一个 context 里面定义了如何解析每种可能 expression 的策略:

struct some_context {
  // an eval template
  template <typename Expr, typename Tag = boost::proto::tag_of<Expr>::type>
  struct eval ;

  // special cases, e.g. terminal
  template <typename Expr>
  struct eval<Expr, boost::proto::tag::terminal> {
    typedef ... result_type ;

    result_type
    operator() (Expr& e, some_context& ctx) const {
      // ... real work here
    }
  } ;
} ;

我们可以回忆一下前面关于 calculator 的例子(见 domain 小节):那里我们并不是直接定义我们的 context,而是使用了继承(CRTP 这个 idiom),这样我们定义的 operator() 通过 tag 和对应参数 bind 到以上类型的调用中。那里我们相当于重定义了每个 terminal 类型 eval 的特化。这样其他的部分就使用默认的实现即可(CRTP 那个例子里面如果去掉子类的实现就会调用父类。)于是我们就能比较清楚的理解 eval 的工作原理了:对最上层推导出类型(比如为 boost::proto::tag::plus),继而调用对应 plus 的 eval,这会导致将对应两个 child expression eval() 的结果进行处理(即相加),而对 child expression 的 eval 又会递归的作用下去(同时也传递对应的 context 引用)。因此我们只需要定义清楚 terminal 的结果以及中间步骤就行了。其他的事情都是编译器帮助我们实现出来的。

为了方便我们提供 context,boost.proto 提供了三个 context 的原型,default_context、null_context 与 callable context(前面提到的 calculator 例子中使用的),他们分别对应于 C++ 原生概念下 eval 结果的 context,eval child 但并不 combine 结果的 context 与允许用户 overload 某些函数改变 default_context 行为的 context(前面分析过了,使用了 CRTP 实现的这个技术)。

其实就算 null_context 也是很有用的,比如可以 override 对 terminal 处理的部分这样就能对一个 expression 的 terminal 进行处理了(事实上使用 callable_context 会更容易一些,但是利用的是 null_context 的 idea)。callable_context 有两个参数,第一个是 CRTP,第二个是 fallback 的 context,默认是 default_context,这就是为什么默认情况下是得到了 default_context 的行为,这样我们将其换为 null_context 就能实现前面所说类似的功能了。

semantic actions

所谓 semantic actions 是指对应于 parser 识别出来符合某些语法的部分后“用户定义的,处理某些事务的代码”,这样就可以在这些情况实现一些自己需要的东西(创建对象?)。boost.proto 也提供给我们了这样的机制,这里面称为 transform。前面使用的许多 grammar 的例子都没有使用 transform。一个比较基本的 transform 是 boost::proto::when 模板,它可以在匹配上某些 tag 类型的时候。事实上,proto 的语法本身除了 match 以外,还可以用来作用在一个 expression 上获得 transform 的结果。看下面的例子

struct my_value : boost::proto::when <
  boost::proto::terminal<boost::proto::_>,
  boost::proto::_value
> {} ;
boost::proto::terminal<int> type ans = {42} ;
BOOST_STATIC_ASSERT ((boost::proto::matches<boost::proto::terminal<int>, my_value>::value)) ;
my_value get_value ;
int i = get_value (ans) ;

通过 boost::proto::or_ 模板可以增强这个 transform 表达的语法。注意下面例子里面特殊的一句话:

struct left_most_child : boost::proto::or_<
  boost::proto::when<
    boost::proto::terminal<boost::proto::_>,
    boost::proto::_value
  >,
  boost::proto::when<
    boost::proto::_,
    left_most_child (boost::proto::_child0)
  >
> {} ;

奇怪的地方就是那个明明应该是 metaprogramming 的地方,也就是说该写 metafunction 的地方却填了一个看起来像函数调用的 left_most_child,这是因为 proto 的 grammar 都是符合 C++TR1 定义的 functor 的,我们知道 functor 一方面可以定义 result_type 表示返回的类型,

struct simple_op {
  typedef int result_type ;
  int
  operator () (int) const {
    return 1 ;
  }
} ;

另外也可以通过一个 inner class

struct complicated_op {
  template <class Signature>
  struct result ;

  template <class This, class T>
  struct result <This (T)> {
    typedef T type ;
  } ;

  template <class T> T
  operator() (T) {
    return T(1) ;
  }
} ;

这样对应的 functor 可以使用 boost::result_of 模板获得返回类型,这样我们可以看出来 left_most_child (boost::proto::_child0) 本质上对应于一个“函数调用类型”,而我们可以针对这种特殊情况利用 boost::result_of 计算出来对应的类型,这样就不用费事写成 metafunction 那种冗长的搞法了。

利用 transform 能够将一些需要的信息“计算”出来,还是拿 calculator 为例,我们知道我们定义了 N 个 _? 类型的 terminal 后,怎么保证用户提供的 context 里面的数据也正好是 N 个呢?我们需要对一个给定的 expression 计算需要的参数个数!ok,基本想法就是如果碰到 terminal 就根据对应的 _? 变成个数,如果是一元操作就取 child0 的,如果是多元操作就取各个中的最大值,这个计算过程是对 type 的计算,必须使用 metafunction,而 boost.mpl 提供了我们这个机制。这样就可以在对应的 calculator expression 中对参数个数进行检查了。

更复杂的 transform

如果我们希望写一个能够 fold 这个 expression tree 里面的 type 的 transform,我们就需要能够 accumulate 扫过的 type,这需要使用 boost::proto::_state,一个拼接的策略是使用 boost.fusion 提供的 boost::fusion::cons 来创建符合 type list 的结构,下面的例子就是利用这个获得

struct fold_to_list : boost::proto::or_<
  boost::proto::when<
    boost::proto::terminal<std::ostream&>,
    boost::proto::_state
  >,
  boost::proto::when<
    boost::proto::termial<boost::proto::_>,
    boost::fusion::cons<boost::proto::_value, boost::proto::_state> (boost::proto::_value, boost::proto::_state)
  >,
  boost::proto::when<
    boost::proto::shift_left<fold_to_list, fold_to_list>,
    fold_to_list (boost::proto::_left, fold_to_list(boost::proto::_right, boost::proto::_state))
  >
> {} ;

除了使用 _state 这类,还可以提供自己的数据,使用 boost::proto::fold 模板进行类似的操作。这里需要注意的是类似 _ 这种表达返回的是当前 expression 相关的信息,如 _value 返回的是 terminal 的 type,_child_c<> 返回的是第 ? 个 child 对应的 expression,类似有 _left、_right、_expr,而 _state 和 _data 就是为了这类操作使用的状态或者数据。由此可见 boost.proto 的语法的双重性,一方面作为 metafunction 可以与 expr 进行 match,另一方面可以作为 functor 进行 transform。

另外有一些更加高级的用法这里并没有涉及,后面有使用的机会的话会一一补上。

——————
But Abram said to Sarai, Behold, your maid is in your hand; do to her as it pleases you. And when Sarai dealt hardly with her, she fled from her face.

Advertisements
proto 高级篇

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s