17

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

附件是一个小例子,我在其中测试了翻转函数的两个参数以添加两个整数。(我使用的编译器是 MinGW 下的 gcc 4.6.2。)在示例中,类型 foraddInt_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 版本(以及“原始”版本)显式转换为适当的 function<> 类型,则可以接受它。

所以我的问题是:

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

  2. 如果有一个根本原因,这两者必须不同。我听说 lambda 可以转换为,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

2 回答 2

41

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

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

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

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

lambda 只是另一个可调用对象,类似于上述some_callable对象的实例。它可以存储在 a 中,std::function因为它是可调用的,但它没有std::function.

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


现在...模板参数推导,或者隐式转换。选一个。这是 C++ 模板的规则。

要将 lambda 作为std::function参数传递,需要对其进行隐式转换。采用std::function参数意味着您选择隐式转换而不是类型推导。但是您的函数模板需要显式推导或提供签名。

解决方案?不要将您的来电者限制在std::function. 接受任何类型的 callable

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接口变得有用。

于 2012-07-24T10:41:38.507 回答
5
  1. 因为function<>采用类型擦除。这允许将几种不同的类似函数的类型存储在 a 中function<>,但会产生很小的运行时损失。类型擦除将实际类型(您的特定 lambda)隐藏在虚函数接口后面。
  2. 这样做有一个好处:C++ 设计“公理”之一是绝不增加开销,除非确实需要。auto使用此设置,您在使用类型推断(使用或作为模板参数传递)时没有任何开销,但您仍然可以灵活地通过function<>. 另请注意,这function<>不是语言结构,而是标准库的一个组件,可以使用简单的语言特性来实现。
  3. 不,但是您可以编写函数以仅采用函数的类型(语言构造)而不是function<>(库构造)的细节。当然,这使得实际写下返回类型变得更加困难,因为它不会直接为您提供参数类型。但是,使用Boost.FunctionTypes等一些元编程,您可以从传入的函数中推断出这些。但在某些情况下这是不可能的,例如使用具有模板的仿函数operator()
于 2012-07-24T10:37:31.927 回答