2012-07-24 125 views
17

我在玩C++ 11的功能特性。我觉得奇怪的一件事是,lambda函数的类型实际上不是函数<>类型。更重要的是,lambda似乎并不能很好地处理类型推入机制。为什么在C++ 11中的lambda函数没有函数<>类型?

附加是一个小例子,我测试了翻转函数的两个参数来添加两个整数。 (我使用的编译器是MinGW下的gcc 4.6.2。)在此示例中,使用函数<>明确定义了addInt_f的类型,而addInt_l是类型为auto的类型推理的lambda。

当我编译的代码,该flip函数可以接受的明确的类型定义addInt的版本,但不是拉姆达版本,给了一个错误说, testCppBind.cpp:15:27: error: no matching function for call to 'flip(<lambda(int, int)>&)'

接下来的几行表明,拉姆达版本(以及“原始”版本)可以被接受,如果它被明确地转换为适当的功能<>类型。

所以我的问题是:

  1. 为什么一个lambda函数没有在首位function<>类型?在这个小例子中,为什么addInt_l不是function<int (int,int)>而是类型?从函数式编程的角度来看,函数/函数对象和lambda之间有什么区别?

  2. 如果有一个根本原因,这两个必须是不同的。我听说拉姆达可以转换为function<>,但它们不同。这是C++ 11的一个设计问题/缺陷,是一个实现问题,还是在区分两者的方式方面有利?似乎只有addInt_l的类型签名提供了有关函数的参数和返回类型的足够信息。

  3. 有没有办法写lambda,以便上述显式类型转换可以避免?

在此先感谢。

//-- testCppBind.cpp -- 
    #include <functional> 
    using namespace std; 
    using namespace std::placeholders; 

    template <typename T1,typename T2, typename T3> 
    function<T3 (T2, T1)> flip(function<T3 (T1, T2)> f) { return bind(f,_2,_1);} 

    function<int (int,int)> addInt_f = [](int a,int b) -> int { return a + b;}; 
    auto addInt_l = [](int a,int b) -> int { return a + b;}; 

    int addInt0(int a, int b) { return a+b;} 

    int main() { 
     auto ff = flip(addInt_f); //ok 
     auto ff1 = flip(addInt_l); //not ok 
     auto ff2 = flip((function<int (int,int)>)addInt_l); //ok 
     auto ff3 = flip((function<int (int,int)>)addInt0); //ok 

     return 0; 
    } 

+4

你不应该服用'std :: function'参数,主要是因为它禁止类型推导(这是你的问题)。 – 2012-07-24 10:22:02

+1

相关:[C++ 11不会在涉及std :: function或lambda函数时推导出类型](http://stackoverflow.com/q/9998402/487781) – hardmath 2012-07-24 10:32:41

+1

将Lambdas转换为匿名函子(或函数,如果它们不要捕捉环境)。将它们转换为std :: function将引入语言和库之间的强大耦合,因此将是一个非常糟糕的主意。 – MFH 2012-07-24 10:33:37

回答

39

std::function商店任何种类的可调用对象不管其类型的有用的工具。为了做到这一点,它需要采用某种类型的擦除技术,并且涉及一些开销。

任何callable可以隐式转换为std::function,这就是为什么它通常无缝工作。

我会重复以确保它变得清晰:std::function不仅仅适用于lambdas或函数指针:它适用于任何类型的可调用函数。例如,这包括诸如struct some_callable { void operator()() {} };之类的内容。这是一个简单的,但它可能是这样的,而不是:

struct some_polymorphic_callable { 
    template <typename T> 
    void operator()(T); 
}; 

一个lambda只是又一个可调用对象,类似于上面的some_callable对象的实例。它可以存储在std::function中,因为它是可调用的,但它不具有类型擦除开销std::function

而且该委员会计划在将来制作多形态lambda,即上面看起来像some_polymorphic_callable的lambda。哪个std::function类型会这样lambda是?


现在...模板参数扣除或隐式转换。选一个。这是C++模板的一个规则。

要传递一个lambda作为std::function参数,它需要隐式转换。采用std::function参数意味着您选择的是类型演绎的隐式转换。但是您的函数模板需要明确推导或提供签名。

解决方案?不要限制您的呼叫者为std::function。接受任何一种可回收

template <typename Fun> 
auto flip(Fun&& f) -> decltype(std::bind(std::forward<Fun>(f),_2,_1)) 
{ return std::bind(std::forward<Fun>(f),_2,_1); } 

您现在可以想为什么我们需要std::function然后。 std::function为具有已知签名的可调品提供了类型擦除。这基本上可以用来存储可擦除类型的可调对象并编写接口。

+0

:谢谢你的明确解释。恕我直言,std :: function只能用于存储的事实相当让人失望。如果我正确理解你的答案,这是为了避免开销?那么,智能指针也一样吗?另外,在你的'some_polymorphic_callable'例子中,我不明白为什么函数<>类型不能表达模板化lambda的类型,因为函数<>可以有模板参数。如果开销是唯一的问题,有没有人认为轻量级函数<>可以用于参数类型?这将使STL更加可用。 – tinlyx 2012-07-26 00:00:37

+0

此外,在'decltype'示例中,如果函数体是25行代码而不是单行内容,那么我们是否需要在decltype中包含25行?另外,使用''看起来几乎像void *或宏。只有在发生编译错误时才能检测到“Fun”的错误使用。相比之下,函数<>参数类型(如果可用)将清楚地告知个人和计算机类型签名。无可否认,我对“类型擦除”等知识非常有限。感谢您告诉我规则。我只是想知道为什么它必须这样。 – tinlyx 2012-07-26 00:14:00

+0

@TingL多态lambda表达式的类型不能表达,因为'function''需要*一个*签名参数,而多态lambda表达式会有*无限数量的不同签名*(这就是为什么它称为多态:它有很多形式)。对于智能指针,开销问题并不完全相同。 'unique_ptr'具有真正的*零*开销(这是设计目标之一)。如果你没有运行多线程程序,'shared_ptr'可能会有一些开销,但是多线程程序更可能被使用。 – 2012-07-26 00:47:14

5
  1. 由于function<>采用type erasure。这允许将几个不同的功能类型类型存储在function<>中,但是会导致运行时间较短。类型擦除隐藏虚拟功能接口后面的实际类型(特定的lambda)。
  2. 这样做是有好处的:C++设计“公理”之一是除非确实需要,否则不会增加开销。使用此设置时,使用类型推断(使用auto或作为模板参数传递)时不会有任何开销,但您仍然可以灵活地通过function<>与非模板代码进行交互。还请注意,function<>不是一种语言结构,而是可以使用简单语言功能实现的标准库的一个组件。
  3. 不,但您可以编写该函数以仅使用函数(语言结构)的类型而不是function<>(库结构)的细节。当然,这使得实际写下返回类型变得困难很多,因为它不直接给你提供参数类型。但是,使用一些元编程a la Boost.FunctionTypes,您可以从您传入的函数中推导出这些函数。有些情况下这是不可能的,例如,仿函数的模板为operator()
相关问题