分类: C/C++
2008-05-28 16:17:46
我们先定义目标:
1. simple_bind 提供与 bind 类似的界面,可以只考虑通过对象引用(或者值)调用成员函数的情况,而不考虑 free function 或者通过指针调用等等。具体地说,就是允许
person.SetName("Ralph") --> simple_bind(&Person::SetName, person, _1)(string("Ralph"))
simple_bind(&Person::SetName, _1, string(“Ralph"))(person)
simple_bind(&Person::SetName, _1, _2)(person, string("Ralph"))
simple_bind(&Person::SetName, _2, _1)(string("Ralph"), person)
2. 一个 simple_bind 表达式会 evaluate 成为一个 simple_binder object ,这是一个 functor ,如:
simple_bind(&Person::SetName, person, _1)(string("Ralph")) --> simple_binder_obj(string("Ralph"))
simple_bind(&Person::SetName, _1, _2)(person, string("Ralph")) --> simple_binder_obj(person, string(“Ralph”))
在这一头一尾,我们其实没多少选择余地,一方面我们需要提供的用户界面已经定了,另一方面需要提供的函数调用界面也基本上固定了,要玩的 magic 也就是发生在中间。
3. 对于 placeholder ,前面已经说过,就用简单的解决方案:
template
class placeholder{};
placeholder<1> _1;
placeholder<2> _2;
有了这3个出发点,现在从 simple_bind 出发,首先看看 simple_bind 应该有的形式。从前面的实验中可以看出
template
simple_binder
simple_bind( R (T::*pfn)(Arg), const Arg& arg, placeholder&)
{
return simple_binder
}
这样的形式虽然方便,但却不具有 scalability 。因为R (T::*pfn)(Arg) 的限制太大了,首先,它只针对接受一个参数的成员函数,如果要支持多个参数,你要对每个参数数量(它有一个术语叫 arity)写一个重载,而这些不同 arity 还要跟 simple_bind 本身的 arity 做组合,结果是爆炸性的 simple_bind 数量。其次,参数 arg 和 placeholder 也太不灵活,前面已经看到,仅仅在两个参数的情况下,就有4种组合:
simple_bind(&Person::SetName, _1, _2)(person, "Joy");
simple_bind(&Person::SetName, _2, _1)("Joy", person);
simple_bind(&Person::SetName, _1, "Joy")(person);
simple_bind(&Person::SetName, person, _1)("Joy");
可想而知参数更多的时候的恐怖情景。于是我们得出结论:
由于前端的调用组合太复杂,simple_bind 的界面必须以最 generic 的方式给出,由此带来的复杂性应该留待 simple_bind 内部处理。
这是经过了实验的教训得出的结论。
何谓“最 generic”?没有比这更加 generic 的了:
template
simple_binder
simple_bind(F f, A1 a1)
{
//...
}
即便如此,由于 C++ 至今还没有可变数量的模板参数,我们还是不能回避对每一个 arity 都有所定义,但是这已经把复杂度控制在 O(arity) 了:
template
simple_binder
simple_bind(F f, A1 a1, A2 a2)
{
//...
}
......
但是随之而来的就是问题:我现在把第一个参数(函数指针)类型笼统成了一个 F ,我如何知道它的返回类型、参数类型和对象类型?好在这个问题可解,其技法在 boost 里面还很常见,那就是 traits :
template
struct result_of
{};
template
struct result_of
{
typedef R type;
};
template
struct result_of
{
typedef R type;
};
template
struct result_of
{
typedef R type;
};
......
它无非利用 C++ 模板元编程最常用的偏特化来“抽取”函数指针的类型内容,我们甚至无需自己写,boost 已经提供好了相应的工具,但是上面的这个 result_of 也还算清晰好用,在这个例子里面,我们就用它。
在这个方向上,思路已经比较开阔了,现在从另一个方向:simple_binder 入手来想一想。simple_binder 应该长成什么样子呢?由于 function_traits 可以帮我们得到函数返回类型,我们可以肯定 simple_binder 在构造的时候已经知道返回类型了,那么它应该是这样吗?
template
class simple_binder
template
class simple_binder
如果你的想法是这样,恭喜你,一出门你就撞了南墙:class 不是 function ,除非你偏特化,它是不允许有多个模板参数“重载”的!但是很显然,这些 simple_binder 之间,谁也不是谁的偏特化。那么是不是可以利用类似 TypeList 的技术,用默认模板参数提供变相的可变参数数量模板:
template
class simple_binder
......
好,这下子 simple_bind 的定义算是完整了:
template
simple_binder
simple_bind(F f, const A1& arg1)
{
//...
}
template
simple_binder
simple_bind(F f, const A1& arg1, const A2& arg2)
{
//...
}
在这个基础上,提供 simple_binder 的构造函数也是举手之劳,
explicit simple_binder(F f, const A1& a1)
: f_(f)
, a1_(a1)
{}
explicit simple_binder(F f, const A1& a1, const A2& a2)
: f_(f)
, a1_(a1)
, a2_(a2)
{}
其中的 f_, a1_, a2_ 都是 simple_binder 的成员。simple_bind 仍然只是简单的构造一个 simple_binder :
template
simple_binder
simple_bind(F f, const A1& arg1)
{
return simple_binder
<
typename result_of
F,
A1
>
(f, arg1);
}
template
simple_binder
simple_bind(F f, const A1& arg1, const A2& arg2)
{
return simple_binder
<
typename result_of
F,
A1,
A2
>
(f, arg1, arg2);
}
至于真正的运算,就留给 simple_binder 和它的助手们去做。我们仍然从最简单的情况开始,研究一下调用时没有参数是什么样,例如这样的调用:
simple_bind(&Person::Name, person)()
这个调用的参数在 simple_bind 的时候已经确定,而在 simple_binder 那里只是简单调用一个0参数的 operator(),也就是:
R operator()()
{
//...
}
即便是这个东西,它代表的意义也不单纯,实际上,在有两个 placeholder 的场合,它代表了两种情况:
1. 类似 simple_bind(&Person::Name, person)(),函数实际上没有参数
2. 类似 simple_bind(&Person::SetName, person, “Joy”)(), 函数其实有一个参数
我们有没有足够的信息来确定是那种情况呢?有,答案就在 simple_binder 的模板参数 A1, A2 中。如果 A2 是 placeholder<0> ,那么就是第一种情况,否则就是第二种情况。为了能在这两种情况之间做出决策,必须加上一个助手,我们称之为 eval :
template
R eval(F f, A1 a1, A2 a2)
{
return (a1.*f)(a2);
}
template
R eval(F f, A1 a1, placeholder<0>)
{
return (a1.*f)();
}
于是乎0参数的 operator() 就成了
R operator()()
{
return eval
}
参数 a2_ 会帮助我们把调用分派到两个 eval 中的一个,从而完成调用决策。
现在来考虑 1 个参数的 operator() ,它就复杂一些了:
1. 类似 bind(&Person::Name, _1)(person) ,函数其实没有参数
2. 类似 bind(&Person::SetName, _1, "Joy")(person),函数其实有1个参数,_1 代表第一个
3. 类似 bind(&Person::SetName, person, _1)("Joy"),函数其实有1个参数,_1 代表第二个
停!!!看来在这个方向上鸣金收兵的时候到了。回顾一下这个设计:
通过提供 generic 的 simple_bind 界面以及 result_of ,我们简化了 simple_bind ;又通过 eval ,我们让 simple_binder 变得单纯起来,然而现在 eval 的复杂性开始膨胀,大有脱离我们控制的趋势。原因在哪里?
参数的复杂性。
作为一个系统,我们的这个东西其实接受两组参数,一组是由 simple_bind 接受的,混杂了 placeholder 和实际参数;另一组是由 simple_binder 的 operator() 接受的,全部都是实际参数,但是其应用的顺序需要根据 placeholder 的类型来决定。了解了这个复杂性的根源,我们的任务再次变得清晰起来:提供一个方法,以统一的方式来处理参数,降低复杂性。