C++ C+中的递归lambda函数+;14
在C++11中编写递归lambda函数有一个经常重复的“技巧”,如下所示:C++ C+中的递归lambda函数+;14,c++,lambda,c++14,C++,Lambda,C++14,在C++11中编写递归lambda函数有一个经常重复的“技巧”,如下所示: std::function<int(int)> factorial; factorial = [&factorial](int n) { return n < 2 ? 1 : n * factorial(n - 1); }; assert( factorial(5) == 120 ); std::函数阶乘; 阶乘=[&阶乘](整数n) {返回nint)。为什么 这与要推导的返回类型是条件表达
std::function<int(int)> factorial;
factorial = [&factorial](int n)
{ return n < 2 ? 1 : n * factorial(n - 1); };
assert( factorial(5) == 120 );
std::函数阶乘;
阶乘=[&阶乘](整数n)
{返回n<2?1:n*阶乘(n-1);};
断言(阶乘(5)==120);
(例如……)
但是这种技术有两个直接的缺点:std::function
对象的目标(通过引用捕获)绑定到一个非常特殊的std::function
对象(这里是factorial
)。这意味着结果函子通常无法从函数返回,否则引用将悬空
另一个(虽然不那么直接)问题是std::function
的使用通常会阻止编译器优化,这是实现中需要类型擦除的副作用。这不是假设性的,很容易测试
在递归的lambda表达式将真的方便的假设情况下,有没有办法解决这些问题?
问题的症结在于,在C++ lambda表达式中,隐式<代码>这个参数总是引用表达式的封闭上下文的对象,如果存在,而不是由lambda表达式生成的函子对象 借用(有时也称为“开放递归”)的一个叶,我们可以使用C++14的通用lambda表达式重新引入一个显式参数,以引用我们可能的递归函子:auto f = [](auto&& self, int n) -> int
{ return n < 2 ? 1 : n * self(/* hold on */); };
这样就可以写:
auto factorial = fix([](auto&& self, int n) -> int
{ return n < 2 ? 1 : n * self(self, n - 1); });
assert( factorial(5) == 120 );
编译器能够优化所有内容,就像使用常规递归函数一样
费用是多少? 精明的读者可能注意到了一个奇怪的细节。在从非泛型到泛型lambda的过程中,我添加了一个显式返回类型(即
->int
)。为什么
这与要推导的返回类型是条件表达式的类型这一事实有关,该类型取决于对self
的调用,该调用正在推导哪种类型。快速阅读将建议按照以下方式重写lambda表达式:
[](auto&& self, int n)
{
if(n < 2) return 1; // return type is deduced here
else return n * self(/* args */); // this has no impact
}
[](自动和自动,int n)
{
如果(n<2)返回1;//这里推导返回类型
else返回n*self(/*args*/);//这没有影响
}
GCC实际上只接受第一种形式的fix_type
(传递functor
的代码)。我无法确定投诉其他表单是否正确(此处传递了*此)。我让读者选择要做的权衡:减少类型推断,或者减少难看的递归调用(当然也完全有可能访问任何一种味道)
GCC 4.9示例
它不是lambda表达式,但几乎没有更多的代码,与C++98一起使用,并且可以递归:
struct{
int运算符()(int n)常量{
返回n<2?1:n*(*此)(n-1);
}
}事实;
返回事实(5);
根据[class.local]/1
,它可以访问封闭函数可以访问的所有名称,这对于成员函数中的私有名称很重要
当然,不是lambda,如果要捕获函数对象外部的状态,必须编写构造函数。Uhm。。。显然,不使用lambda表达式是这里的自然选择…是的。编写一个普通的独立C++函数;LucDanton:如果你选择在C++中编写递归lambda,那么你选择的很差。如果您可以在函数中编写一个类,那么就没有理由这样做。是的,它会更乏味,但它仍然比你做递归lambdas所需要的体操容易。@LucDanton:真的吗?大部分都在支持组件中,但是需要编写lambda才能在该框架中工作,并且您必须在调用fix
时使用它。并不是说它很恐怖或者什么的,但这看起来好像兰姆达是新的金锤,这是在把手上加了一个螺丝刀头…@dyp,有些东西不会太难展开。。。一个成员函数奇数
,另一个成员函数偶数
,然后是执行初始分派的操作符()
(我必须承认我只是盲目猜测,我不知道你指的是什么偶数/奇数函数),同时它解释了为什么一般来说不能轻松创建递归lambda,我不明白为什么不允许简单的无捕获lambda。毕竟,它们可以被认为是一个简单的函数,只是具有局部作用域。老实说,这是递归lambda函数最实用的答案。:)
00000000004005e0 <main>:
4005e0: b8 78 00 00 00 mov eax,0x78
4005e5: c3 ret
4005e6: 66 90 xchg ax,ax
[](auto&& self, int n)
{
if(n < 2) return 1; // return type is deduced here
else return n * self(/* args */); // this has no impact
}
struct {
int operator()(int n) const {
return n < 2 ? 1 : n * (*this)(n-1);
}
} fact;
return fact(5);