proto 再探(续)

前面介绍了怎么使用 proto 的 FE 进行一些声明。这里介绍一些如何利用获得的 composite 做一些事情。

expr 模板

通过 boost::proto::terminal 这类声明的 terminal 本质上都是 boost::proto::expr<> 模板(type generator 的典型例子),这个模板有三个参数,通过 tag 标识这个 expression 的类型(如 boost::proto::tag::terminal 或者别的 plus 等),第二个参数(Arg)是实际的类型,如 boost::proto::term<> 或者 boost::proto::list?<> 等,最后一个是表示为“几元”操作,比如 terminal 一般是 0,一元的比如有 negate 等。这样我们的 Arg 一般是定义过 child? 类型的 struct,比如 term 一般定义 child0,而如果是别的就会有几个。前面定义自己的函数的时候使用的 tag 是 function,functor 类型本身作为第二个参数,最后一个仍然是表示是几元操作。

为了判断一个 expr<> 的类型,我们可以用 tag_of<> 获得其 tag 的类型(这样方便我们优化?)。而使用 arity_of<> 就能拿到对应的参数个数。每个 terminal 是存在对应 value 的,如下面的例子

boost::proto::terminal<std::ostream&>::type cout_ = {std::cout} ;
std::ostream& sout = boost::proto::value (cout_) ;

我们也可以用 boost::result_of::value<> 获得对应的类型。

为了抽象的方便我们访问一个 expr<> 的内部 children,一种可能的策略就是使用 child_c<> 模板,我们也可以使用 result_of::child_c<> 获得对应的类型,为了方便,也提供了 child()、left() 和 right() 对应 0 和 1 的情况。

一个比较常见的操作是对 expression 进行 deep copy,不少临时变量都有一些引用可能在释放后变成 dangling reference,因此往往直接赋值不能将 reference 对应的对象进行 copy,使用 boost::proto::deep_copy 就能解决这个问题。

与 fusion 的关系

expression 可以被 fusion 的 algortihm 处理,下面是来自 manual 上的例子。

#include <iostream>
#include <boost/proto/proto.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/transform.hpp>
#include <boost/fusion/include/pop_front.hpp>

namespace bp = boost::proto ;
namespace bf = boost::fusion ;

struct display {
  template <class T> inline void
  operator() (T const& t) const {
    std::cout << t << std::endl ;
  }
} ;

struct fun_t {} ;
bp::terminal<fun_t>::type const fun = {{}} ;
bp::terminal<int>::type const _1 = {1} ;

int
main (int, char**) {
  bp::display_expr ( fun (1, 2, 3, 4)) ;

  bf::for_each (
    bf::transform (
      bf::pop_front (fun (1, 2, 3, 4)),
      bp::functional::value ()
    ),
    display ()
  );

  bp::display_expr (_1 + 2 + 3 + 4) ;

  bf::for_each (
    bf::transform (
      bp::flatten (_1 + 2 + 3 + 4),
      bp::functional::value ()
    ),
    display ()
  );

  return 0 ;
}

这里通过 bf::pop_front 去掉了 fun(1, 2, 3, 4) 这个表达的第一个 child,通过 bp::functional::value 这个 functor 的 functor(返回 expr 的 bp::value() 的结果的 functor),这个结果通过 bf::for_each 调用 display 这个 functor 显示出来。另一个例子是利用 bp::flatten 将一个 tree 结构压平后输出。这里的一个问题是 g++ 4.2 似乎不能给出正确结果(难道是苹果的 c++ 编译器的 bug…)。

Grammar

本质上 grammar checking 就是将两个 expression tree 根据 tag 进行比较,看看是否一致,因此可以用 boost::proto::op 这个模板(op 可以是一些 operator,如 plus 等)与给定的 Expr 的进行比较,这可以用 boost::proto::match 这个模板进行,这样就能判断是否是加法乘法等等。这个方法的弱点是只能判定一层的结构是否合法,可想而知上一篇文章中使用 boost::proto::or_<> 模板利用 CRTP 搞了不少递归性规则的作用,其实还有别的如 not_<>/if_<> 和 _<> 是如何实现的,这些逻辑运算符表明哪些 expression 匹配上是合法的/非法的。

那么一个复杂的 expression tree 是如何判断每层是否合法呢?(也就是说怎么写一个 match 模板呢)一个简单的想法就是取当前的 tag,然后与规则集里面的比较(这一般是通过 template 特化实现的),合法后遍历每个 child expression(使用 child_c 模板),递归的调用下去即可。

围了提高编译时效率,boost.proto 提供了 switch_ 方案,这个大致的意思是说,我们定义一个 grammar cases 类,其中有一个所谓的 case_ 成员结构模板,它默认继承了 boost::proto::not_<boost::proto::_>,也就是说不接受任何语法。然后我们将 or_ 里面的各种情形用模板特化的方式加入,下面是一个大致的意思,

// forward declaration of my_grammar
struct my_grammar ;

struct my_grammar_cases {
  // by default it accepts none
  template <typename Tag>
  struct case_ : boost::proto::not_<boost::proto::_> {} ;
} ;

template <>
struct my_grammar_cases::case_<boost::proto::tag::terminal>
  : boost::proto::or_<
    // terminal cases ...
  > {} ;

// allowed ops
template <>
struct my_grammar_cases::case_<boost::proto::tag::plus>
  : boost::proto::plus<my_grammar, my_grammar> {} ;
// more...

struct my_grammar : boost::proto::switch_<my_grammar_cases> {} ;

这些语法里面比较奇怪的是一个所谓的 operator(),它可以接受任意多个参数,我们一般用 boost::proto::function<tag, boost::proto::vararg<terminal> > 形式。在做语法检查的时候使用 boost::proto::nary_expr<boost::proto::_, boost::proto::vararg<GrammarName> >。

语法定义之后我们常在 evaluation 的 facade 进行检查,然后调用 implementation 进行具体的计算(很可能是递归调用)。

简单的例子

为了让大家对上面一些叙述有个比较具体的理解,下面是一个利用这种层次结构干活的例子:比如我们希望如下创建 map,

std::map <std::string, std::string> m ;
fill_map (m,
  map ("hello", "world") ("just", "kidding")
) ;

这个意思就是将 map 那个 expression 通过 fill_map 这个操作作用在一个 map 上。为此我们的语法是

struct map_ {} ;
bp::terminal<map_>::type map = {} ;

struct map_grammar
  : bp::or_<
    bp::terminal<map_>,
    bp::function<
      map_grammar,
      bp::terminal<char const*>,
      bp::terminal<char const*>;
    >
> {} ;

我们在 fill_map 里面需要利用这个语法做检查,并且递归的作用下去,

typedef std::map<std::string, std::string> map_t ;

template <class Expr> void
fill_map (map_t& m, const Expr& e) {
  BOOST_MPL_ASSERT_MSG(
    (bp::matches<Expr, map_grammar>::value),
    INPUT_EXPRESSION_DOES_NOT_MATCH_MAP_GRAMMAR,
    (map_grammar)) ;
  fill_map_imp (m, e) ;
}

template <class Expr> void
fill_map_imp (map_t& m, const Expr& e) {
  m [bp::value (bp::child_c<1> (e))] = bp::value (bp::child_c<2> (e)) ;
  fill_map_imp (m, bp::child_c<0> (e)) ;
}

void
fill_map_imp (map_t& m, const bp::terminal<map_>::type& e) {}

从某种角度来说像 boost.assign 这类辅助初始化容器的工具很适合使用 boost.proto 来实现,类似的例子在 boost.program_options 里面声明 option_descriptions 的也有看到。

——————
And Sarai Abram’s wife took Hagar her maid the Egyptian, after Abram had dwelled ten years in the land of Canaan, and gave her to her husband Abram to be his wife.

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