(看过了这么多编译期算法之后)还记得运行时吧?我们已经在编译期的天空飞行了好久,现在是时候脚踏实地了。一个有趣的程序终究还是要在运行时干点什么的。 本章就是关于怎样穿越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 的模版参数里面传送的。