C++构造自动推导模板类型的function adaptor(STL Function Adaptor)
继续前一篇文章http://deltamaster.is-programmer.com/posts/41833.html关于构造function adaptor的话题。
template <typename operation> operation derivative_functor(operation f, typename operation::argument_type dx) { typedef typename operation::argument_type T; return [=](T x)->T { return (f(x + dx) - f(x)) / dx; }; }
这是前一篇文章最后构造的function adaptor。回忆一下,尽管我们不再需要在调用时指定lambda表达式的参数类型T,但是我们仍然需要指定operation的类型。从C++语法上讲,函数模板的调用应该是支持类型推导的,也就是说,如果调用derivative_functor的时候,没有指定operation的类型,C++编译时也会自动推导。可惜这种情况下自动推导的结果是错误的,以至于无法通过编译。
再看一下调用语句。
typedef function<double (double)> operation; cout << derivative_functor<operation>(my_sqr_functor<double>(), 0.000001)(5) << endl;
在这里我们给出的参数是my_sqr_functor<double>(),那么经过自动推导得到的operation类型就是my_sqr_functor<double>,尽管这个lambda表达式和my_sqr_functor<double>都分别兼容于类型function<double (double)>,但是它们两者之间没有继承关系,因此难以完成转换。
为了解决这个问题,我们就要避免出现这种不兼容的类型转换,只管的解决办法就是,令这个adaptor返回另一种类型的对象(而不是lambda表达式),这种对象的类型与operation指定的类型类似,并且同样可以像函数一样使用,兼容function<T (T)>类型。到这里我们必须要放弃lambda表达式,而用functor来模拟这个lambda表达式的行为了。
首先刚才说到,operation的类型和adaptor的返回类型有共性,下面让我们来构造这个共性的部分。
template<typename Arg, typename Result> class my_unary_function { public: typedef Arg argument_type; typedef Result result_type; private: virtual Result operator()(Arg arg) = 0; };
这两者都可以当做一元函数来使用,并且我们用typedef定义了argument_type和result_type了,关于这个typedef的使用前一篇已经有一些说明。
template<typename T> class my_sqr_functor_derived : public my_unary_function<T, T> { public: T operator()(T x) { return (x * x); } };
这个求平方的functor应该继承自这个my_unary_function,因为它“是”一个一元函数,这个继承关系会将父类的typedef一并继承下来。子类的T的实际类型会被代入父类my_unary_function的Arg和Result,成为自己的argument_type和result_type。
接下来就是重点的adaptor本身的改造了。
template <typename operation> class derivative_functor_derived : public my_unary_function<typename operation::argument_type, typename operation::result_type> { private: operation op; typename operation::argument_type dx; public: derivative_functor_derived(operation f, typename operation::argument_type x) : op(f), dx(x) {} typename operation::result_type operator()(typename operation::argument_type x) { return (op(x + dx) - op(x)) / dx; } };
首先这个adaptor现在是一个对象,它也“是”一个一元函数,因此同理,也继承自my_unary_function,并且指定恰当的模板参数。两个private对象,op和dx,就是先前lambda表达式中,从函数scope中传递下来的f和dx,我们在构造这个对象的时候,通过构造函数,将我们指定的op和dx保存到本对象的数据成员中,在括号运算符重载函数中,就可以使用op和dx了。至此,我们的adaptor是一个具有一元函数性质的对象,可以直接使用,而避免了不兼容类型的转换。
等一下!C++的语法不支持类模板的类型推导,这意味着我们调用这个构造函数的时候还是必须要指定的,像下面这样。
cout << derivative_functor_derived<my_sqr_functor_derived<double> >(my_sqr_functor_derived<double>(), 0.000001)(5) << endl;
我们刚才消除不兼容的类型转换,是为了省去模板参数的指定!别担心,注意到现在模板参数和传递的参数类型已经一致了吗?那现在我们只需要一个wrapper函数来帮助我们完成心愿。
template <typename operation> derivative_functor_derived<operation> derivative_functor_derived_wrapper(operation f, typename operation::argument_type dx) { return derivative_functor_derived<operation>(f, dx); }
这个函数所做的唯一的事情,就是调用了derivative_functor_derived的构造函数,不过因为这是一个函数模板,根据我们传递的f参数类型,就可以自动推导正确的operation类型了!
cout << derivative_functor_derived_wrapper(my_sqr_functor_derived<double>(), 0.000001)(5) << endl;
这里就是最终形式了。回想上一篇开头的STL Function Adaptor调用,看看是不是有些类似呢?
cout << count_if(v.begin(), v.end(), bind2nd(less<int>(), 12)) << endl;
我们的derivative_functor_derived_wrapper和STL函数模板bind2nd的行为完全一致。这就是STL Function Adaptor的实现原理。
至此,我们了解了STL Function Adaptor的设计思想和实现,并且能够使用functor来模拟一些lambda表达式的行为。