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

deltamaster posted @ Nov 30, 2013 09:35:42 AM in C++ with tags c++ template lambda adaptor , 1987 阅读

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

	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>()是同样的含义。

 

* 本文在CC BY-SA(署名-相同方式共享)协议下发布。

登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter