简体   繁体   English

C++14 中的递归 lambda 函数

[英]Recursive lambda functions in C++14

There is an oft-repeated 'trick' to write recursive lambda functions in C++11 that goes as follows:在 C++11 中编写递归 lambda 函数有一个经常重复的“技巧”,如下所示:

std::function<int(int)> factorial;
factorial = [&factorial](int n)
{ return n < 2 ? 1 : n * factorial(n - 1); };

assert( factorial(5) == 120 );

(eg Recursive lambda functions in C++0x .) (例如C++0x 中的递归 lambda 函数。)

This technique has two immediate drawbacks though: the target of the std::function<Sig> object is tied (via the capture by reference) to a very particular std::function<Sig> object (here, factorial ).但是,这种技术有两个直接的缺点: std::function<Sig>对象的目标(通过引用捕获)绑定到一个非常特殊的std::function<Sig>对象(此处为factorial )。 This means that the resulting functor typically cannot be returned from a function, or else the reference would be left dangling.这意味着结果函子通常不能从函数返回,否则引用将悬空。

Another (although less immediate) problem is that the use of std::function is typically going to prevent compiler optimizations, a side-effect of the need for type-erasure in its implementation.另一个(虽然不那么直接)问题是std::function的使用通常会阻止编译器优化,这是在其实现中需要类型擦除的副作用。 This is not hypothetical and can easily be tested.这不是假设的,可以很容易地进行测试。

In the hypothetical situation where recursive lambda expressions would really be convenient, is there a way to address those issues?在递归 lambda 表达式真的很方便的假设情况下,有没有办法解决这些问题?

The crux of the issue is that in a C++ lambda expression the implicit this parameter will always refer to the object of the enclosing context of the expression, if present at all, and not the functor object resulting from the lambda expression.问题的关键在于,在 C++ lambda 表达式中,隐式this参数将始终引用表达式的封闭上下文的对象(如果存在的话),而不是由 lambda 表达式产生的函子对象。

Borrowing a leaf from anonymous recursion (sometimes also known as 'open recursion'), we can use the generic lambda expressions of C++14 to re-introduce an explicit parameter to refer to our would-be recursive functor:借用匿名递归(有时也称为“开放递归”)的叶子,我们可以使用 C++14 的通用 lambda 表达式重新引入一个显式参数来引用我们的递归函子:

auto f = [](auto&& self, int n) -> int
{ return n < 2 ? 1 : n * self(/* hold on */); };

The caller now has a new burden of making calls of the form eg f(f, 5) .调用者现在有一个新的负担来进行诸如f(f, 5)形式的调用。 Since our lambda expression is self-referential, it is in fact a caller of itself and thus we should have return n < 2 ? 1 : n * self(self, n - 1);由于我们的 lambda 表达式是自引用的,它实际上是自身的调用者,因此我们应该return n < 2 ? 1 : n * self(self, n - 1); return n < 2 ? 1 : n * self(self, n - 1); . .

Since that pattern of explicitly passing the functor object itself in the first position is predictable, we can refactor this ugly wart away:由于在第一个位置显式传递函子对象本身的模式是可以预测的,我们可以重构这个丑陋的疣:

template<typename Functor>
struct fix_type {
    Functor functor;

    template<typename... Args>
    decltype(auto) operator()(Args&&... args) const&
    { return functor(functor, std::forward<Args>(args)...); }

    /* other cv- and ref-qualified overloads of operator() omitted for brevity */
};

template<typename Functor>
fix_type<typename std::decay<Functor>::type> fix(Functor&& functor)
{ return { std::forward<Functor>(functor) }; }

This allows one to write:这允许一个人写:

auto factorial = fix([](auto&& self, int n) -> int
{ return n < 2 ? 1 : n * self(self, n - 1); });

assert( factorial(5) == 120 );

Did we succeed?我们成功了吗? Since the fix_type<F> object contains its own functor which it passes to it for each call, there is never a risk of a dangling reference.由于fix_type<F>对象包含自己的函子,每次调用都会传递给它,因此永远不会有悬空引用的风险。 So our factorial object can truly be endless copied, moved from, in and out of functions without hassle.因此,我们的factorial对象可以真正地被无限复制、从函数中移出、移入和移出函数,而不会出现任何麻烦。

Except... while the 'external' callers can readily make calls of the form factorial(5) , as it turns out inside our lambda expression the recursive call still looks like self(self, /* actual interesting args */) .除了...虽然“外部”调用者可以很容易地调用形式factorial(5) ,但事实证明在我们的 lambda 表达式内部,递归调用仍然看起来像self(self, /* actual interesting args */) We can improve on this by changing fix_type to not pass functor to itself, but by passing *this instead.我们可以通过改变fix_type来改进fix_type ,不将functor传递给它自己,而是传递*this That is, we pass in the fix_type object which is in charge of passing the correct 'implicit-as-explicit' argument in the first position: return functor(*this, std::forward<Args>(args)...);也就是说,我们传入负责在第一个位置传递正确的“隐式为显式”参数的fix_type对象: return functor(*this, std::forward<Args>(args)...); . . Then the recursion becomes n * self(n - 1) , as it should be.然后递归变成n * self(n - 1) ,它应该是。

Finally, this is the generated code for a main that uses return factorial(5);最后,这是为使用return factorial(5);main生成的代码return factorial(5); instead of the assertion (for either flavour of fix_type ):而不是断言(对于fix_type任何一种):

00000000004005e0 <main>:
  4005e0:       b8 78 00 00 00          mov    eax,0x78
  4005e5:       c3                      ret    
  4005e6:       66 90                   xchg   ax,ax

The compiler was able to optimize everything away, as it would have done with a run-off-the-mill recursive function.编译器能够优化所有内容,就像使用普通的递归函数一样。


What are the costs?费用是多少?

The astute reader may have noticed one curious detail.精明的读者可能已经注意到一个奇怪的细节。 In the move from a non-generic to a generic lambda, I added an explicit return type (ie -> int ).在从非泛型到泛型 lambda 的转变过程中,我添加了一个显式返回类型(即-> int )。 How come?怎么来的?

This has to do with the fact that the return type to be deduced is the type of the conditional expression, which type depends on the call to self , which type is being deduced.这与要推导的返回类型是条件表达式的类型这一事实有关,哪种类型取决于对self的调用,推导的是哪种类型。 A quick reading of Return type deduction for normal functions would suggest that rewriting the lambda expression as follows should work:快速阅读普通函数返回类型推导会表明重写 lambda 表达式如下应该工作:

[](auto&& self, int n)
{
    if(n < 2) return 1;               // return type is deduced here
    else return n * self(/* args */); // this has no impact
}

GCC will in fact accept this code with the first form of fix_type only (the one that passes functor ).实际上,GCC 将仅接受带有第一种形式的fix_type代码(通过functor )。 I'm not able to determine if it is right to complain about the other form (where *this is passed).我无法确定抱怨其他形式( *this已通过)是否正确。 I leave it to the reader to choose what trade-off to make: less type deduction, or less ugly recursive calls (it's also of course completely possible to have access to either flavour anyway).我让读者选择权衡取舍:减少类型推导,或者减少丑陋的递归调用(当然,无论如何也完全有可能获得任何一种风格)。


GCC 4.9 examples GCC 4.9 示例

It's not a lambda expression, but hardly more code, works with C++98, and can recurse:它不是一个 lambda 表达式,但几乎没有更多的代码,适用于 C++98,并且可以递归:

struct {
    int operator()(int n) const {
        return n < 2 ? 1 : n * (*this)(n-1);
    }
} fact;
return fact(5);

According to [class.local]/1 , it has access to all names that the enclosing function has access to, which is important for private names in a member function.根据[class.local]/1 ,它可以访问封闭函数有权访问的所有名称,这对于成员函数中的私有名称很重要。

Of course, not being a lambda, you have to write a constructor if you want to capture state outside the function object.当然,不是 lambda,如果要捕获函数对象之外的状态,则必须编写构造函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM