【问题标题】:Different stack depth for lambdas and regular functions in C++?C ++中lambda和常规函数的不同堆栈深度?
【发布时间】:2017-02-03 16:57:54
【问题描述】:

考虑一个普通的递归函数:

#include <iostream>
#include <functional>

void f(unsigned long long int x) {
    std::cout << x << "\n";
    if(x < 1e9)
        f(x+1);
}

int main() {
    f(1);
    return 0;
}

这终止于 43033

现在考虑一个递归 lambda:

#include <iostream>
#include <functional>

int main() {
    std::function<void(int)> g = [&g](unsigned long long int x) {
        std::cout << x << "\n";
        if(x < 1e9)
            g(x+1);
    };
    g(1);
    return 0;
}

这会在 11736 的堆栈深度低得多时终止。

为什么 lambda 的最大堆栈深度较低?

(使用g++ (GCC) 5.4.0编译,使用-std=c++14 -Wall

另请注意,使用-O3 优化进行编译允许几乎无限递归深度,但 lambda 仍终止于 25k


编辑:在@Yakk 之后,这里是Y-combinator 的结果:

#include <iostream>
#include <functional>

using namespace std;

template <typename T, typename R>
function<R(T)> Y(function<function<R(T)>(function<R(T)>)> f) {
    // Y f = f (λx.(Y f) x)
    return f([=](T x) { return Y(f)(x); });
}

int main() {
    using fg = function<void(int)>;
    function<fg(fg)> sg = [](fg g) {
        return [g](unsigned long long int x) {
            std::cout << x << "\n";
            if(x < 1e9)
                g(x+1);
        };
    };

    Y(sg)(1);
    return 0;
}

这在 47819221 分别有和没有-O3 终止。

【问题讨论】:

  • @DieterLücking 详细说明?
  • 尝试直接使用 lambda (auto f) 我认为开销在std::function
  • @Motti 它不会编译,因为它需要事先知道函数签名才能递归!
  • @prakharsingh95,好点,反正@Yakk 说开销在std::function 中,而不是在 lambda 中。
  • 栈可以容纳 N 个字节。您可以在堆栈中放入 M 个小部件或 K 个小工具。小部件和小工具占用不同的字节数。又是什么问题?

标签: c++ function recursion lambda


【解决方案1】:

std 函数与 lambda 的含义不同。 std 函数是一个能够存储一些 lambda 表达式的对象,或者一个函数指针,或者一个指向成员函数的指针,或者一个指向成员数据的指针,或者几乎任何兼容地覆盖 operator() 的对象。

在 std 函数中存储 lambda 时,会产生一些开销。不多,但有一些。其中一些开销可能表现为更多地使用堆栈(并且在未优化的构建中开销会更大)。

您可以通过使用 y combinator 更直接地使用 lambda 进行递归,但即使在那里,您也会将对自身的引用作为参数传递,除非优化器消除了递归,否则它可能会使用更多堆。 (经过高度调整的优化器可能会注意到可以消除无状态 lambda 引用参数,但这似乎很难解决)。

【讨论】:

  • AFAIK, std::function 是编写递归 lambda 的唯一方法。如果你能举个例子那就太好了!
  • std::function 调用有一个额外的参数:Effectively does INVOKE(f, std::forward&lt;Args&gt;(args)..., R)(引用自en.cppreference.com/w/cpp/utility/functional/function/…
  • @underscore_d 我也添加了结果。我还是不清楚。
  • @prak 链接到堆栈溢出时添加的 lambda 的许多 y 组合器之一。有很多变化。同样,不需要使用std::functionstd::function 用于类型擦除,我们所做的任何事情都不需要类型擦除。无论如何你问为什么递归深度更短,现在应该清楚原因了吧?
猜你喜欢
  • 2015-04-14
  • 2019-12-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-20
  • 1970-01-01
  • 2021-07-03
  • 2011-04-29
  • 1970-01-01
相关资源
最近更新 更多