2015-02-08 179 views
1

(1)中的lambda表达式的类型是什么?在C++中递归调用通用lambda表达式的类型14

为什么可以编译此代码?

#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(......)>)>)>

请指出我的错误。

+1

由于您明确提到了lambda的返回类型,因此编译器无需查看递归调用'f(f,a-1)'的路径。如果你删除了' - > int',你会遇到问题。 – 0x499602D2 2015-02-08 02:24:23

回答

3

第一个错误:std::function是一类无关的任何拉姆达。

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

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

由于您无法命名lambda类型,因此使用std::function进行存储是很常见的。然而,拉姆达不是std::function。从它们的类型擦除和多态性开始,几乎不可避免地产生开销:lambda缺少任何多态性,这使得编译器很容易理解调用时的作用。

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

你的第一拉姆达是:

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

这看起来像Y型组合子的形式,或变体,用来帮助与递归。 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首先调用它。这可以让你将递归绑定在“视线之外”。使内拉姆达mutable是可选的(取决于如果你想在一个const上下文来调用)总之

,接下来的拉姆达:

[](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的实例传递给第一个,加上一个参数,然后计算n!

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

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

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

致电您的第一个lambda g,您的第二个h和我的lambda y和lambda my lambda返回z

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


注意,你可以写std::function支持递归式签名,就像std::function< std::vector<SELF_TYPE>(int) >。你可以看到boost::variant如何与递归变体一起工作。

1

从[expr.prim.lambda],重点煤矿:

的 拉姆达返回类型为auto其由尾返回型取代,如果提供和/或推断出从 返回语句如7.1.6.4所述。

您提供尾随收益型,那就是在你的代码的->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占位符类型的类型来确定的表达式的类型, 程序是病态的。但是,一旦在函数中看到返回语句,则从该语句中推导出的返回类型 可用于函数的其余部分,包括其他返回语句。
[实施例:

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 
} 

末端示例]