C++ C+中的递归lambda函数+;14

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)。为什么 这与要推导的返回类型是条件表达

在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)
{返回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);