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