C ++ 14中递归调用泛型lambda表达式的类型

时间:2015-02-08 02:06:51

标签: c++ recursion lambda c++14

(1)中lambda表达式的类型是什么?

为什么这段代码可以编译?

#include<functional>
#include<iostream>


int main() {
    std::cout << 
        [](auto&& f0,auto&& a0){return f0(f0,a0);}
        (
            [](auto& f,auto&& a)->int{ return (a>1) ? f(f,a-1)*a : 1; }, // (1)
            5
         )
         << std::endl;
}

我认为无限递归是由此代码中lambda表达式(1)的类型推断引起的。 我认为auto& f被替换为类型名称,例如std::function<int(std::function<int(std::function<int(......)>)>)>

请指出我的错误。

2 个答案:

答案 0 :(得分:3)

第一个错误:std::function是一种与任何lambda无关的类型。

lambda是一种匿名类型,具有operator()和一些其他已知属性。

std::function<R(Args...)>是复制构造的类型擦除类,使用Args...销毁和调用并返回R。它可以用lambda构造,但不是相关的类型。

由于您无法命名lambda的类型,因此使用std::function存储它很常见。然而,lambda不是std::functionstd::function在类型擦除和多态性方面几乎不可避免地产生了开销:lambdas缺乏任何多态性,这使编译器很容易理解()在调用时的作用。

在你的情况下,你有两个lambdas。

你的第一个lambda是:

[](auto&& f0,auto&& a0){return f0(f0,a0);}

这看起来像是y-combinator或变体的一种形式,用于递归的帮助。在这种情况下operator()有签名:

template<class F0, class A0>
auto operator()(F0&&,A0&&)const
-> std::result_of_t<F0&(F0&,A0&)>

大致。

更有用的版本(在我看来)是:

[](auto&& f0){
  return [f0=std::forward<decltype(f0)>(f0)]
    (auto&&...args) {
      return f0(f0, std::forward<decltype(args)>(args)...);
    };
}

接受f0,存储它,并使用先传递f0的任何参数调用它。这可以让你将递归“看不见”。使内部lambda mutable是可选的(取决于你是否想要在const上下文中调用)

无论如何,下一个lambda:

[](auto& f,auto&& a)->int{ return (a>1) ? f(f,a-1)*a : 1; }

operator()签名为:

template<class F, class A>
auto operator()(F&,A&&)const
-> int

然后将第二个lambda的实例传递给第一个lambda,加上一个参数,然后计算n!

template运算符()推导出的类型不依赖于参数本身推导出的类型,因此没有无限类型推导问题。内部lambda的返回类型被硬编码为int,因此您不必推断()递归返回以知道它返回int

但是,如果你想将第一个lambda存储在std::function中,你会感到很失望。 std::function无法删除template operator():它只能删除固定的签名,template成员是方法的工厂,而不是方法本身。

但是,请记住我上面的y组合的更好版本吗?

调用你的第一个lambda g,你的第二个h和我的lambda y以及lambda我的lambda返回z

然后g(h,x) = y(h)(x) - 和y(h)可以存储在std::function<int(int)>没问题。我们隐藏了基本上需要递归类型签名的递归部分,std::function不支持 1 。剩下的内容虽然有template operator(),但可以绑定到一个简单的签名。


1 请注意,您可以编写std::function来支持递归签名,例如std::function< std::vector<SELF_TYPE>(int) >。您可以看到这对boost::variant如何使用递归变体的方式有用。

答案 1 :(得分:1)

来自[expr.prim.lambda],强调我的:

  

在   lambda返回类型为auto trailing-return-type 替换,如果提供和/或从   返回7.1.6.4中描述的语句。

您提供了 trailing-return-type ,即代码中的->int,因此不必进行类型扣除。返回类型只是int

但是,即使没有->int,如果您只提供if语句而不是使用条件运算符,仍然可以编译函数:

auto f = [](auto& f0, auto&& a) {
    if (a <= 1) {
        return 1; // this *must* be the first return case.
    }
    else {
        return f0(f0, a-1) * a;
    }
};

std::cout << f(f, 5) << std::endl; // prints 120

这种情况,只有这种情况符合上述§7.1.6.4[dcl.spec.auto]中提到的规则之一:

  

如果需要具有undeduced占位符类型的实体类型来确定表达式的类型,   该计划格式不正确。但是,在函数中看到return语句后,返回类型   从该语句中推导出的可用于函数的其余部分,包括其他返回语句    [例如:

 auto sum(int i) {
     if (i == 1)
         return i; // sum’s return type is int
     else
         return sum(i-1)+i; // OK, sum’s return type has been deduced
 }
     

-end example]