C++ 11中的递归lambda函数

wei*_*ima 127 c++ lambda c++11

我是C++ 11的新手.我正在编写以下递归lambda函数,但它不编译.

sum.cpp

#include <iostream>
#include <functional>

auto term = [](int a)->int {
  return a*a;
};

auto next = [](int a)->int {
  return ++a;
};

auto sum = [term,next,&sum](int a, int b)mutable ->int {
  if(a>b)
    return 0;
  else
    return term(a) + sum(next(a),b);
};

int main(){
  std::cout<<sum(1,10)<<std::endl;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译错误:

vimal @ linux-718q:〜/ Study/09C++/c ++ 0x/lambda> g ++ -std = c ++ 0x sum.cpp

sum.cpp:在lambda函数中:sum.cpp:18:36:错误:' ((<lambda(int, int)>*)this)-><lambda(int, int)>::sum'不能用作函数

gcc版本

gcc版本4.5.0 20091231(实验性)(GCC)

但如果我改变sum()下面的声明,它的作用是:

std::function<int(int,int)> sum = [term,next,&sum](int a, int b)->int {
   if(a>b)
     return 0;
   else
     return term(a) + sum(next(a),b);
};
Run Code Online (Sandbox Code Playgroud)

有人可以点亮这个吗?

小智 162

考虑自动版本和完全指定的类型版本之间的区别.该自动关键字推断出它的类型无论从那个它与初始化,但你与需要初始化它知道它的类型是什么什么(在这种情况下,拉姆达闭合需要知道它的捕获类型).鸡和蛋的问题.

另一方面,完全指定的函数对象的类型不需要"知道"关于分配给它的内容的任何内容,因此lambda的闭包同样可以完全了解其捕获的类型.

考虑对代码的这种轻微修改,它可能更有意义:

std::function<int(int,int)> sum;
sum = [term,next,&sum](int a, int b)->int {
if(a>b)
    return 0;
else
    return term(a) + sum(next(a),b);
};
Run Code Online (Sandbox Code Playgroud)

显然,这不适用于汽车.递归lambda函数运行得非常好(至少它们在MSVC中,我对它们有经验),它只是它们与类型推断不兼容.

  • @DeadMG但规范禁止在其初始化程序中引用`auto`变量.在处理初始化程序时,尚未知道自动变量的类型. (15认同)
  • 我不同意这一点.一旦输入函数体,lambda的类型就是众所周知的 - 没有理由不应该在那时推断它. (3认同)
  • 想知道为什么这没有被标记为“答案”,而 Python 被归类为“答案”?! (2认同)
  • @Puppy:但是,在隐式捕获的情况下,为了提高效率,实际上仅捕获引用的变量,因此必须解析主体。 (2认同)

Joh*_*erg 58

诀窍是将lambda实现作为参数提供给自身,而不是通过捕获.

const auto sum = [term,next](int a, int b) {
  auto sum_impl=[term,next](int a,int b,auto& sum_ref) mutable {
    if(a>b){
      return 0;
    }
    return term(a) + sum_ref(next(a),b,sum_ref);
  };
  return sum_impl(a,b,sum_impl);
};
Run Code Online (Sandbox Code Playgroud)

计算机科学中的所有问题都可以通过另一层次的间接解决.我首先在http://pedromelendez.com/recursive-lambdas-in-c14/找到了这个简单的技巧

确实需要C++ 14而问题出在C++ 11上,但对大多数人来说可能都很有趣.

继续进行std::function也是可能的,但可能导致代码变慢.但不总是.看看std :: function vs template的答案

  • 这比std :: function更好,原因有三:它不需要类型擦除或内存分配,它可以是constexpr,它可以正常使用自动(模板化)参数/返回类型 (11认同)
  • 据推测,此解决方案还具有可复制的优点,而不会超出范围的 std::function 引用? (3认同)
  • @JohanLundberg它仅在函数中有另一个返回时才起作用(因此可以推断出返回类型)——在示例中已经有一个“return 0”,因此编译器可以推断出返回类型是“int”——在一般情况下指定返回类型是必要的。 (3认同)
  • 对于 `auto g=[&amp;](auto g){ return g(g); }; g(g);`,需要指定返回类型。 (3认同)
  • 这似乎比显式使用function &lt;&gt;更糟。我不明白为什么有人会喜欢它。编辑:显然是更快。 (2认同)
  • 嗯,在尝试时,GCC 8.1(linux)抱怨:“错误:在扣除“自动”之前使用了[[...]” –需要明确指定返回类型(另一方面,不需要可变) 。 (2认同)

Bar*_*rry 30

使用C++ 14,现在很容易制作一个有效的递归lambda,而不必std::function在几行代码中产生额外的开销(从原始代码进行少量编辑以防止用户意外复制):

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        // [edit: Barry] pass in std::ref(*this) instead of *this
        return f(std::ref(*this), std::forward<Args>(args)...);
    }
};

// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}
Run Code Online (Sandbox Code Playgroud)

您的原始sum尝试成为:

auto sum = make_y_combinator([term,next](auto sum, int a, int b) {
  if (a>b) {
    return 0;
  }
  else {
    return term(a) + sum(next(a),b);
  }
});
Run Code Online (Sandbox Code Playgroud)

  • @minex是的,`auto sum`复制...但它复制一个`reference_wrapper`,这与获取引用是一样的。在实现中执行一次意味着任何用途都不会意外复制。 (3认同)

Yan*_*kes 22

我有另一种解决方案,但只能使用无状态lambda:

void f()
{
    static int (*self)(int) = [](int i)->int { return i>0 ? self(i-1)*i : 1; };
    std::cout<<self(10);
}
Run Code Online (Sandbox Code Playgroud)

这里的诀窍是lambdas可以访问静态变量,你可以将无状态转换为函数指针.

您可以将它与标准lambdas一起使用:

void g()
{
    int sum;
    auto rec = [&sum](int i) -> int
    {
        static int (*inner)(int&, int) = [](int& _sum, int i)->int 
        {
            _sum += i;
            return i>0 ? inner(_sum, i-1)*i : 1; 
        };
        return inner(sum, i);
    };
}
Run Code Online (Sandbox Code Playgroud)

它在GCC 4.7中的工作

  • 这应该比std :: function具有更好的性能,所以+1替代.但实际上,在这一点上我想知道使用lambdas是否是最好的选择;) (3认同)
  • 如果你有一个无状态的 lambda,你也可以将它设为一个完整的函数。 (2认同)
  • @Timmmm但是你会将部分实现泄漏到外部,通常 lambda 与父函数紧密耦合(即使没有捕获)。如果不是这种情况,那么您不应该首先使用 lambda 并使用函子的普通函数。 (2认同)

Yan*_*kes 11

在 C++23 中,添加了显式对象参数( P0847:推导此):

auto f = [](this auto& self, int i) -> int
{
    return i > 0 ? self(i - 1) + i : 0;
}
Run Code Online (Sandbox Code Playgroud)

目前,它仅在 Clang 和 EDG eccp 中可用,并且在 MSVC 中部分可用:

https://godbolt.org/z/f3E3xT3fY


Zuz*_*uza 10

可以递归地进行lambda函数调用.你唯一需要做的就是通过函数包装器引用它,以便编译器知道它的返回和参数类型(你不能捕获一个变量 - lambda本身 - 尚未定义) .

  function<int (int)> f;

  f = [&f](int x) {
    if (x == 0) return 0;
    return x + f(x-1);
  };

  printf("%d\n", f(10));
Run Code Online (Sandbox Code Playgroud)

要非常小心,不要超出包装f的范围.

  • 但是,这与接受的答案相同,并且可能因使用std函数而受到惩罚. (2认同)

Ori*_*ent 8

要在不使用外部类和函数(如std::function定点组合器)的情况下使lambda递归,可以在C++ 14(实例)中使用以下结构:

#include <utility>
#include <list>
#include <memory>
#include <iostream>

int main()
{
    struct tree
    {
        int payload;
        std::list< tree > children = {}; // std::list of incomplete type is allowed
    };
    std::size_t indent = 0;
    // indication of result type here is essential
    const auto print = [&] (const auto & self, const tree & node) -> void
    {
        std::cout << std::string(indent, ' ') << node.payload << '\n';
        ++indent;
        for (const tree & t : node.children) {
            self(self, t);
        }
        --indent;
    };
    print(print, {1, {{2, {{8}}}, {3, {{5, {{7}}}, {6}}}, {4}}});
}
Run Code Online (Sandbox Code Playgroud)

打印:

1
 2
  8
 3
  5
   7
  6
 4
Run Code Online (Sandbox Code Playgroud)

注意,应明确指定lambda的结果类型.

  • 这里唯一看起来实际上有用的答案。 (2认同)

mmo*_*cny 6

我使用std::function<>捕获方法运行了比较递归函数和递归lambda函数的基准.在clang 4.1版上启用完全优化后,lambda版本的运行速度明显变慢.

#include <iostream>
#include <functional>
#include <chrono>

uint64_t sum1(int n) {
  return (n <= 1) ? 1 : n + sum1(n - 1);
}

std::function<uint64_t(int)> sum2 = [&] (int n) {
  return (n <= 1) ? 1 : n + sum2(n - 1);
};

auto const ITERATIONS = 10000;
auto const DEPTH = 100000;

template <class Func, class Input>
void benchmark(Func&& func, Input&& input) {
  auto t1 = std::chrono::high_resolution_clock::now();
  for (auto i = 0; i != ITERATIONS; ++i) {
    func(input);
  }
  auto t2 = std::chrono::high_resolution_clock::now();
  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
  std::cout << "Duration: " << duration << std::endl;
}

int main() {
  benchmark(sum1, DEPTH);
  benchmark(sum2, DEPTH);
}
Run Code Online (Sandbox Code Playgroud)

产生结果:

Duration: 0 // regular function
Duration: 4027 // lambda function
Run Code Online (Sandbox Code Playgroud)

(注意:我还确认了一个从cin获取输入的版本,以便消除编译时评估)

Clang还会生成编译器警告:

main.cc:10:29: warning: variable 'sum2' is uninitialized when used within its own initialization [-Wuninitialized]
Run Code Online (Sandbox Code Playgroud)

这是预期的,安全的,但应该注意.

在我们的工具带中有一个解决方案很棒,但我认为如果性能与当前方法相当,语言将需要更好的方法来处理这种情况.

注意:

正如一位评论者所指出的,似乎VC++的最新版本已经找到了一种方法来优化它以达到同等性能的程度.也许我们毕竟不需要更好的方法来解决这个问题(除了语法糖).

此外,正如其他一些SO帖子在最近几周所概述的那样,std::function<>自身的性能可能是直接调用减速与直接调用函数的原因,至少当lambda捕获太大而无法适应std::function小型仿函数的某些库优化空间用途时(我想有点像各种短字符串优化?).

  • -1.请注意,"lambda"版本需要更长时间的唯一原因是因为您将它绑定到std :: function,这使得operator()调用虚拟调用,这显然需要更长时间.最重要的是,在VS2012发布模式下,您的代码在两种情况下花费的时间大致相同. (2认同)
  • 确认.我误解了你的帖子.+1然后.加,如果你编辑这个答案,只能投票.你能不能再强调一点,比如在评论中? (2认同)