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

C++使用Lambda表达式做function adaptor

我们往往有许多现成的函数(或者是具有函数行为的对象),有些时候,我们希望通过改造这些函数来生成新的函数来为我们所用。

	int myArr[] = {1, 1, 2, 3, 5, 8, 13, 21};
	vector<int> v(myArr, myArr + sizeof(myArr) / sizeof(int));
	cout << count_if(v.begin(), v.end(), bind2nd(less<int>(), 12)) << endl;

这段代码中less<int>()是临时functor对象,而bind2nd负责将这个接受两个参数比较他们大小的函数,将第二个参数绑定为12,改造成接受一个参数的functor,就可以被count_if所使用了。这段忙代码帮忙数出vector中小于12的项的数量。

我曾经在博客中写过利用Javascript闭包来实现的改造器。

function derivative(f,  dx) {
    return  function (x) {
        return (f(x + dx) - f(x)) / dx;
    };
}
 
var result = derivative(function(x) {
    return x * x;
}, 0.000001)(5);

这个derivative是一个改造器,接受参数是一个函数f,返回其导函数。具体解释请参考http://deltamaster.is-programmer.com/posts/28768.html

下面我们要用C++的Lambda表达式来实现类似功能。

现有一个非常简单的函数,求x的平方,和上面Javascript中的例子相同。

double my_sqr(double x)
{
	return (x * x);
}

下面我们写一个改造器。

function<double (double)> derivative_simple(function<double (double)> f, double dx)
{
	return [=](double x)->double {
		return (f(x + dx) - f(x)) / dx;
	};
}

这个改造器是一个函数,接受的第一个参数是一个函数指针,或者其他具有函数行为的对象,这种对象作为函数使用的时候,接受一个double类型的参数,返回double类型。这个改造器的返回类型和它的第一个参数类型相同。

在我们返回的Lambda表达式中,我们从改造器的scope把f和dx都以值传递方式传递进来。这个Lambda表达式根据输入参数x,求出这一点的导数值。

然后我们调用它,并接着直接调用其返回的Lambda表达式,得到x=5这一点的导数值。如我们所愿,我们看到了10(微积分还没有忘光吧)。

cout << derivative_simple(my_sqr, 0.000001)(5) << endl;

不过有一点不够理想的是,这个改造器的适用范围太狭窄了,如果我需要求导的函数接受的不是double类型的参数,而是Stone类型(假设这种神奇的Stone能进行数值计算),我们就要再写另一个差不多的改造器。这里你应该差不多想到了,我们可以用模板来实现。

template <typename operation, typename T>
operation derivative_any(operation f, T dx)
{
	return [=](T x)->T {
		return (f(x + dx) - f(x)) / dx;
	};
}

大家可以比较这里的operation和T出现的位置,和上面的例子比较一下。然后我们调用它。

	typedef function<double (double)> operation;
	cout << derivative_any<operation, double>(my_sqr, 0.000001)(5) << endl;

我们在调用之前用typedef做了一个类型声明,简化下面调用语句的书写。这次调用的效果还是一样的。但是如果将来真的要把Stone传进来,我们只要将operation指定为function<Stone (Stone)>,将T指定为Stone就可以了。

到这里就有一个小问题了,因为其实operation里的参数和返回类型,应该和T指定的类型总是相同的。按照上面的写法,淘气的程序猿完全可以指定不同类型,而造成不必要的错误。在编译时期发现错误还好,如果是发生了非预期的隐式类型转换而造成运行时错误,就很难发现了。

这样就要求我们能够在operation里携带一些类型信息,再将这个类型信息在改造器中取出,取代现在T的位置。不过一个普通的函数没有办法隐含这样的信息,于是我们需要把这个求平方的函数本身变成一个functor,以便保存额外的类型信息。

template<typename T>
class my_sqr_functor
{
public:
	typedef T argument_type;
	T operator()(T x)
	{
		return (x * x);
	}
};

在这里我们用typedef来把类模板参数T定义为argument_type类型。想象一下,当这个模板被展开的时候,T被各种不同的类型替换,比如double,这个时候我们用typedef,将double在类的范围内定义为了argument_type。这样,我们在这个类外也总是能够通过对象的argument_type来获得当初指定给T的类型。

下面修改我们的改造器。

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;
	};
}

改造器也相应升级了,我们在这里不需要再指定模板参数T了,而是用typename operation::argument_type来代替。下面再将它定义成T纯粹是为了书写方便。改造器的功能基本没有变化。然后我们调用它。

	typedef function<double (double)> operation;
	cout << derivative_functor<operation>(my_sqr_functor<double>(), 0.000001)(5) << endl;

调用方法和上面稍有不同的,一方面我们不再能够指定那个模板参数T,另一方面my_sqr_functor<double>不再是一个函数,而是一个模板类,直接在后面加上括号调用其默认构造函数构造了一个临时对象,这种写法和最开头看到的less<int>()是同样的含义。