为啥 c++11 中的 lambda 函数没有 f​​unction<> 类型?

Posted

技术标签:

【中文标题】为啥 c++11 中的 lambda 函数没有 f​​unction<> 类型?【英文标题】:why do lambda functions in c++11 not have function<> types?为什么 c++11 中的 lambda 函数没有 f​​unction<> 类型? 【发布时间】:2012-07-22 15:31:49 【问题描述】:

我在玩 c++11 的功能特性。我觉得奇怪的一件事是 lambda 函数的类型实际上不是 function 类型。更重要的是,lambda 似乎不能很好地与类型推断机制配合使用。

附件是一个小例子,我在其中测试了翻转函数的两个参数以添加两个整数。 (我使用的编译器是 MinGW 下的 gcc 4.6.2。)在示例中,addInt_f 的类型已使用 function 显式定义,而 addInt_l 是一个 lambda,其类型使用 auto 进行类型推断。

当我编译代码时,flip 函数可以接受显式类型定义的 addInt 版本,但不能接受 lambda 版本,给出错误提示, testCppBind.cpp:15:27: error: no matching function for call to 'flip(&lt;lambda(int, int)&gt;&amp;)'

接下来的几行表明,如果将 lambda 版本(以及“原始”版本)显式转换为适当的函数类型,则可以接受它。

所以我的问题是:

    为什么 lambda 函数首先没有 function&lt;&gt; 类型?在这个小例子中,为什么addInt_l 没有function&lt;int (int,int)&gt; 作为类型,而是使用不同的lambda 类型?从函数式编程的角度来看,函数/函数对象和lambda有什么区别?

    如果有根本原因,这两者必须不同。我听说 lambda 可以转换为 function&lt;&gt;,但它们是不同的。这是 C++11 的设计问题/缺陷、实现问题还是将两者区分开来是否有好处?似乎仅addInt_l 的类型签名就已经提供了有关函数的参数和返回类型的足够信息。

    有没有办法编写 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;
    

【问题讨论】:

您不应该使用std::function 参数,主要是因为它禁止类型推断(这是您的问题)。 相关:C++11 does not deduce type when std::function or lambda functions are involved Lambda 被转换为匿名函子(或函数,如果它们不捕获环境)。将它们转换为 std::function 会在语言和库之间引入强耦合,因此将是一个非常糟糕的主意。 @MFH Lambda 始终是匿名函数对象。然后可以将这些函数对象转换为函数指针。 @TingL 这就是你错的地方。 lamdas 不是库结构,而是一种新的语言特性。 【参考方案1】:

std::function 是一个用于存储任何类型的可调用对象的工具,无论其类型如何。为了做到这一点,它需要使用某种类型擦除技术,这会带来一些开销。

任何可调用对象都可以隐式转换为std::function,这就是它通常无缝工作的原因。

我会重复一遍以确保它变得清晰:std::function 不仅仅适用于 lambda 或函数指针:它适用于 any 类型的可调用。例如,这包括 struct some_callable void operator()() ; 之类的东西。这是一个简单的,但它可能是这样的:

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

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

委员会计划在未来使 lambdas 具有多态性,即看起来像上面的 some_polymorphic_callable 的 lambdas。这样的 lambda 会是哪种std::function 类型?


现在...模板参数推导,或隐式转换。选一个。这是 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::functionstd::function 为具有已知签名的可调用对象提供类型擦除。这基本上使得存储类型擦除的可调用对象和编写virtual 接口变得有用。

【讨论】:

:感谢您的清晰解释。恕我直言,std::function 只能用于存储的事实相当令人失望。如果我正确理解您的答案,这是为了避免开销?那么,智能指针也一样吗?此外,在您的 some_polymorphic_callable 示例中,我不明白为什么 function 类型不能表达模板化 lambda 的类型,因为 function 已经可以具有模板参数。如果开销是唯一的问题,是否有人认为可以在参数类型中使用轻量级函数?这将使 STL 更有用。 另外,在decltype 的例子中,如果函数体是25 行代码而不是一行代码,我们需要在decltype 中包含25 行代码吗?此外,&lt;typename Fun&gt; 的使用对我来说几乎就像 void* 或宏。 Fun 的误用只有在发生编译错误时才能被检测到。相比之下,函数 参数类型(如果可用)会清楚地告诉人和计算机类型签名。诚然,我对“类型擦除”等的了解非常有限。谢谢你告诉我规则。我只是想知道为什么必须这样。 @TingL 无法表达多态 lambda 的类型,因为 function&lt;&gt; 采用 一个 签名参数,而多态 lambda 将具有 无限数量的不同签名(这就是它被称为多态的原因:它有多种形式)。开销问题并不完全适用于智能指针。 unique_ptr 具有真正的 开销(这是设计目标之一)。如果您没有运行多线程程序,shared_ptr 可能会产生一些开销,但多线程程序更有可能被使用。 我的示例中的 decltype 不是强制性的。我使用它是因为它省去了我自己计算返回类型的麻烦。如果函数有很多行,您可以只在decltype 中放置类似返回的表达式(毕竟这对于计算返回类型很重要)。但有时它确实有点毛茸茸:( 我很同情您担心发现错误。遗憾的是,概念提案(本来可以解决该问题)还没有真正准备好,并且没有包含在标准的本次修订中。不过,目前你可以通过 static_assertis_callable trait 得到很好的错误(遗憾的是不在标准库中,所以你有 to write your own)。【参考方案2】:
    因为function&lt;&gt; 雇用type erasure。这允许将几种不同的类似函数的类型存储在function&lt;&gt; 中,但会产生很小的运行时损失。类型擦除将实际类型(您的特定 lambda)隐藏在虚函数接口后面。 这样做有一个好处:C++ 设计“公理”之一是绝不会增加开销,除非确实需要。使用此设置,您在使用类型推断时没有任何开销(使用auto 或作为模板参数传递),但您仍然可以通过function&lt;&gt; 与非模板代码进行交互。另请注意,function&lt;&gt; 不是一种语言结构,而是标准库的一个组件,可以使用简单的语言特性来实现。 不可以,但您可以编写函数以仅采用函数的类型(语言构造)而不是 function&lt;&gt;(库构造)的细节。当然,这使得实际写下返回类型变得更加困难,因为它不会直接为您提供参数类型。但是,使用 Boost.FunctionTypes 等一些元编程,您可以从传入的函数中推断出这些。但在某些情况下这是不可能的,例如具有模板化 operator() 的函子。

【讨论】:

以上是关于为啥 c++11 中的 lambda 函数没有 f​​unction<> 类型?的主要内容,如果未能解决你的问题,请参考以下文章

没有花括号的 JavaScript 中的 Lambda 函数语法

为啥 lambda 函数默认会丢弃推导的返回类型引用?

C++11 中的递归 lambda 函数

C ++中的Lambda函数,参数和逻辑[重复]

为啥在 C++ 中将较大函数中的某些功能编写为 lambdas?

在 C++11 中不允许重新定义 lambda,为啥?