C++ Template Metaprogramming 第九章试译: Crossing the Compile-Time/Runtime Boundary (1)

来源:互联网 发布:宁夏干部网络培训教育 编辑:程序博客网 时间:2024/06/05 16:03



(看过了这么多编译期算法之后)还记得运行时吧?我们已经在编译期的天空飞行了好久,现在是时候脚踏实地了。一个有趣的程序终究还是要在运行时干点什么的。 本章就是关于怎样穿越C++编译期和运行时的边界——这一层“臭氧层”,如果你想要的话——这样我们的元程序可以真正的用户面前施展拳脚。在C++中,进 行这趟旅程的办法恐怕是无穷无尽,但是其中有一些更加有用,下面讲到的就是最常用的一些技巧。

9.1 for_each

STL 里最简单的算法在MPL里面也应该有,也的确有。先回顾一下:std::for_each 遍历一个(运行时的)序列,并且对每一个元素调用某个(运行时的)仿函数。类似的,mpl::for_each 遍历一个编译期的序列,并且调用运行时仿函数。尽管 std::for_each 是完全运行时的,mpl::for_each 却是一个混合体,横跨在编译期和运行时世界之间。


为什么用运行时的仿函数?
如 果你在想,为什么 mpl::for_each 要采用一个运行时的仿函数,而不是一个元函数呢?那么考虑一下这个:一般来说,std::for_each 用到的仿函数返回 void,即便它真的返回一个什么值,那个值也是被忽略的。换言之,如果这个仿函数真的要做点什么事情,那它一定要以某种方式修改程序的状态。然而,函数 式编程 (functional programming) 是天生无状态的,而元编程是函数式编程,所以对一个序列的每个元素调用某个元函数并没有多大意义,除非我们要对返回的结果做点什么。


9.1.1 类型打印 (Type Printing)

你想过要打印你的类型序列 (type sequences) 的内容么?如果我们用的编译器可以输出可读的 std::type_info::name ,那我们就可以这么干:

struct print_type
{
  template <class T>
  void operator()(T) const
  {
    std::cout << typeid(T).name() << std::endl;
  }
};

typedef mpl::vector<int, long, char*> s;
int main()
{
  mpl::for_each<s>(print_type());
}

这 段代码有几个东西值得你注意:首先,print_type 的 operator() 是个模板,因为它必须能处理出现在我们的序列中的任何类型。除非你要处理的序列的所有元素都能转化成某一个类型,否则 mpl::for_each 的仿函数就必须有一个模板化的 operator() 。

其次,注意 for_each 传给仿函数的是其每个类型的一个值初始化 (value-initialized) 对象。这种形式在序列元素都是整数常量 wrapper 的时候非常方便。然而,对别的类型就要小心了,如果那个元素是个引用,或者是没有缺省构造函数的类型,又或者干脆就是个 void,这个算法就没办法编译,因为它们都不能被值初始化。

我们可以避免这个问题,只要加上一个小小的 wrapper:

template <class T>
struct wrap {};

// 包含引用
typedef mpl::vector<int&, long&, char*&> s;
 
mpl::for_each<
  mpl::transform<s, wrap<_1> >::type
>(print_type());

我们也要调整仿函数的签名,让它能接受新的参数:

struct print_type
{
  template <class T>
  void operator()(wrap<T>) const  // 推导出 T
  {
    std::cout << typeid(T).name() << std::endl;
  }
};

由于这个用法如此常见,MPL提供了第二种形式的 for_each ,它接受一个转换元函数作为第二参数。采用这种形式,我们就不用自己 wrap 序列了:

mpl::for_each<s, wrap<_1> >(print_type());

对 s 中的每一个元素 T,print_type 都被调用,参数是 wrap<T>。

9.1.2 类型访问 (Type Visitation)


如果需要更加通用的方法来解决“函数调用边界上的”类型问题,我们可以采用 Vistor 模式。

struct visit_type   //generalized visitation function object
{
  template <class Visitor>
  void operator()(Visitor) const
  {
    Visitor::visit();
  }
};

template <class T>  //specific visitor for type printing
struct print_visitor
{
  static void visit()
  {
    std::cout << typeid(T).name() << std::endl;
  }
};

int main()
{
  mpl::for_each<s, print_visitor<_1> >(visit_type());
}

在 这里,仿函数 visit_type 期望它的参数类型拥有一个 static 成员函数 visit ,我们可以让 visitor 对象做任何事情。相对于前面的 for_each 例子来说,这是一个很小的变化,但是注意:print_visitor::visit 从来就没有接受一个真正的 T 类型的对象,相反地,对于序列中的每一个 T,for_each 都把一个 print_visitor<T> 类型的实例传递给 visit_type,而关于 T 的类型信息是在 print_visitor 的模版参数里面传送的。
原创粉丝点击