Chinaunix首页 | 论坛 | 博客
  • 博客访问: 18671324
  • 博文数量: 7460
  • 博客积分: 10434
  • 博客等级: 上将
  • 技术积分: 78178
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-02 22:54
文章分类

全部博文(7460)

文章存档

2011年(1)

2009年(669)

2008年(6790)

分类: 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(pfn, arg);

}

这样的形式虽然方便,但却不具有 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 返回值的类型*/, typename F, typename A1>

class simple_binder

template

class simple_binder

如果你的想法是这样,恭喜你,一出门你就撞了南墙:class 不是 function ,除非你偏特化,它是不允许有多个模板参数“重载”的!但是很显然,这些 simple_binder 之间,谁也不是谁的偏特化。那么是不是可以利用类似 TypeList 的技术,用默认模板参数提供变相的可变参数数量模板:

template , typename A3 = placeholder<0> /*...*/ >

class simple_binder
......


好,这下子 simple_bind 的定义算是完整了:

template

simple_binder::type, F, A1>

simple_bind(F f, const A1& arg1)

{

//...

}

template

simple_binder::type, F, A1, A2>

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::type, F, A1>

simple_bind(F f, const A1& arg1)

{

    return simple_binder

        <

        typename result_of::type,

        F,

        A1

        >

    (f, arg1);

}

 

template

simple_binder::type, F, A1, A2>

simple_bind(F f, const A1& arg1, const A2& arg2)

{

    return simple_binder

        <

        typename result_of::type,

        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(f_, a1_, a2_);

    }

参数 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 的类型来决定。了解了这个复杂性的根源,我们的任务再次变得清晰起来:提供一个方法,以统一的方式来处理参数,降低复杂性。

阅读(525) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~