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

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

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

	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(署名-相同方式共享)协议下发布。
cleaners in dubai 说:
Feb 23, 2020 01:05:51 PM

It can be easy enough to discover at least a number of commercial carpet cleaners service companies in the area just by simply looking inside yellow internet pages, checking your advertising part of your magazine, or conducting a search on-line. The next step is to find out more about every one of the professional carpet cleaners companies you are looking at so that you feel confident that you've chosen the most appropriate one. Following are generally some tips that may help you decide.


登录 *


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