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表达式的行为。