【问题标题】:what is the type signature of a c++11/1y lambda function?c++11/1y lambda 函数的类型签名是什么?
【发布时间】:2014-02-09 09:53:08
【问题描述】:

我想知道是否有一种标准方法来获取任何给定 lambda 的参数的类型签名(即返回类型和类型)?

我问的原因是我一直想知道 auto 在声明中的确切类型是什么,例如 auto l =[](int x,int y)->int{return x+y;}。在auto 的其他用例中,对于较长的类型名称,它是一种方便且较短的替代方案。但是对于 lambdas,是否还有另一种声明 lambda 变量的方法?

我的理解是,一个标准的 lambda 只不过是一个函数对象,它是它自己的类型。因此,即使两个 lambda 具有相同的返回类型和参数类型,它们仍然是两个不同的、不相关的类/函子。但这有办法捕捉它们在类型签名方面相同的事实吗?

我认为我正在寻找的类型签名可能类似于正确类型的 std::function<> 对象。

一个更有用/涉及的问题是,如果可以提取类型签名,则可以编写一个通用包装函数将任何 lambda 函数转换为具有相同类型签名的 std::function 对象。

【问题讨论】:

    标签: c++ c++11 lambda


    【解决方案1】:

    根据Can the 'type' of a lambda expression be expressed?,实际上在当前的c++中有一个简单的方法(不需要c++1y)来计算一个lambda的return_type和参数类型。适应这一点,为每个 lambda 组装一个std::function 类型签名类型(下面称为f_type)并不困难。

    我。使用这种抽象类型,实际上可以有另一种方式来表达 lambda 的类型签名,而不是 auto,即下面的 function_traits<..>::f_type。注意:f_type 不是 lambda 的真正类型,而是函数式 lambda 类型签名的摘要。然而,它可能比 lambda 的真实类型更有用,因为每个 lambda 都是它自己的类型

    如下代码所示,既可以使用vector<int>::iterator_type i = v.begin(),也可以使用function_traits<lambda>::f_type f = lambda,这是对神秘的auto的一种替代。当然,这种相似性只是形式上的。下面的代码涉及将 lambda 转换为 std::function,其代价是构造 std::function 对象时的类型擦除成本以及通过 std::function 对象进行间接调用的少量成本。但是除了使用std::function 的这些实现问题(我认为这不是基本的并且应该永远存在)之外,毕竟有可能明确表达任何给定lambda的(抽象)类型签名。

    二。也可以编写一个make_function 包装器(很像std::make_pairstd::make_tuple)以自动将lambda f(和其他可调用对象,如函数指针/函子)转换为std::function,具有相同的类型- 演绎能力。

    测试代码如下:

    #include <cstdlib>
    #include <tuple>
    #include <functional>
    #include <iostream>
    using namespace std;
    
    // For generic types that are functors, delegate to its 'operator()'
    template <typename T>
    struct function_traits
        : public function_traits<decltype(&T::operator())>
    {};
    
    // for pointers to member function
    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) const> {
        //enum { arity = sizeof...(Args) };
        typedef function<ReturnType (Args...)> f_type;
    };
    
    // for pointers to member function
    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) > {
        typedef function<ReturnType (Args...)> f_type;
    };
    
    // for function pointers
    template <typename ReturnType, typename... Args>
    struct function_traits<ReturnType (*)(Args...)>  {
      typedef function<ReturnType (Args...)> f_type;
    };
    
    template <typename L> 
    typename function_traits<L>::f_type make_function(L l){
      return (typename function_traits<L>::f_type)(l);
    }
    
    long times10(int i) { return long(i*10); }
    
    struct X {
      double operator () (float f, double d) { return d*f; } 
    };
    
    // test code
    int main()
    {
        auto lambda = [](int i) { return long(i*10); };
        typedef function_traits<decltype(lambda)> traits;
        traits::f_type ff = lambda;
    
        cout << make_function([](int i) { return long(i*10); })(2) << ", " << make_function(times10)(2) << ", " << ff(2) << endl;
        cout << make_function(X{})(2,3.0) << endl;
    
        return 0;
    }
    

    【讨论】:

      【解决方案2】:

      您说得对,C++11 lambda 的类型是匿名且实例唯一的。 std::function 类型可以存储对我遇到的任何类型的 lambda 的引用,但据说会影响性能。

      试试

      std::function<int (int, int)> f = [](int x, int y) -> int { 
          return x + y; 
      };
      

      请注意,-&gt; int 在诸如此类的非模棱两可的情况下可以省略。

      C++14 让我们编写

      std::function<int (int, int)> f = [](auto x, auto y) { 
          return x + y; 
      };
      

      这对于长类型名称很方便。

      正如@Jonathan Wakely 所指出的,这种方法使用带有固定模板参数的 std::function 来捕获特定的实例化。在 C++14 中,可以指定模板变量。此外,同样根据 C++14,lambda 参数可以通过auto 推断其类型,允许执行以下操作:

      template<class T>
      std::function<T (T, T)> g = [](auto x, auto y) -> auto {
          return x + y;
      };
      

      目前,VC++ 和 GCC 似乎不支持函数级别的变量声明模板,但允许在成员、命名空间和全局声明中使用模板。我不确定这个限制是否来自规范。

      注意:我不使用clang。

      【讨论】:

      • A std::function 具有固定的调用签名(例如您的示例中的 int(int,int)),而通用 lambda 的闭包类型可能接受不同的参数,例如[](auto... x) { }),因此尽管您可以将其存储在 std::function 中,但您限制了它的功能
      • 我没有考虑即将发布的标准中的通用 lambda。但是,我使用 std::function 实例将闭包传递给其他函数。但实际上,由于他们计划添加模板化变量声明,在下一个标准中,应该能够编写:template&lt;class T&gt; std::function&lt; T (T, T) &gt; f = (auto x,auto y) {return x+y;};
      • 有趣,我没有想过使用这样的变量模板。
      • 是的,它是一个奇怪的新功能。特别是因为 auto 关键字仅在用作参数的类型时,才遵循模板的类型推断规则,而不是 auto 变量声明的规则。
      【解决方案3】:

      在 C++1y 中,有通用 lambda,没有单个调用签名(operator()() 是一个模板)。

      【讨论】:

      • 我认为你的意思是 C++14。我不确定新的operator()() 模板是否意味着 lambda 在结构上是可等价的。不过,我可能已经过时了。
      • @Aluan:在批准投票之前没有“C++14”。 C++1y 是预期成为 C++14 的草案。最终投票仍有可能做出一些改变。
      • 啊,我明白了。谢谢你的澄清。
      【解决方案4】:

      我想知道是否有一种标准方法可以获取任何给定 lambda 的参数的类型签名(即返回类型和类型)?

      不,没有。

      我问的原因是我一直想知道 auto 在声明中到底是什么类型 auto l =[](int x,int y)-&gt;int{return x+y;}

      这是一个未指定的类类型,由实现创建。 lambdas 的全部意义在于它们是“匿名函数”,即你不知道它们的类型。

      如果你想要一个已知类型,那就写一个函数对象类型。

      在 auto 的其他用例中,对于较长的类型名称,它是一种方便且较短的替代方案。但是对于 lambdas,是否还有另一种声明 lambda 变量的方法?

      没有。

      如果您想自己声明类型,请不要使用 lambda 表达式。

      我的理解是,一个标准的 lambda 只不过是一个函数对象,它是它自己的类型。因此,即使两个 lambda 具有相同的返回类型和参数类型,它们仍然是两个不同的、不相关的类/函子。

      正确。每个 lamda 表达式都会生成一个唯一的类型。

      但这有办法捕捉它们在类型签名方面相同的事实吗?

      不,没有语言功能允许这样做。

      我认为我正在寻找的类型签名可以是正确类型的 std::function 对象。

      即使在 C++11 中可行,但在 C++14 中也无济于事,因为 lambda 表达式可以采用任何数量和任何类型的参数,例如[](auto... a) { }

      无论如何,如果您不知道 lambda 函数的调用签名,那么我会说您使用 lambda 错误。当您编写 lambda 时,您应该知道它的属性是什么,因此当您知道它的属性时,要么立即使用它,要么尽早将其放入 std::function(或捕获其调用签名的其他类型)中。如果您在不知道调用签名的地方创建 lambda 并在非本地使用它们,那么您做错了。

      【讨论】:

      • “不,没有语言功能允许这样做。”这相当于说,至少来自 C++1y 的多态 lambda 不能被模板化,我不确定这是否正确,我想我之前已经看到过与新 C++1y 功能相关的模板化 lambda用于多态 lambda。
      • “不,没有语言特性允许这样做” 但是在en.cppreference.com/w/cpp/utility/functional/function/… 的示例中,我们看到封装在 std::function 中时编码为名称的 lambda 函数的类型。假设这样的名称是唯一的,这似乎是一种查看两种类型是否相同的方法。对吗?
      • @ZviDan,没有。因为我可以做std::function&lt;void(int)&gt; f( [](long) { return true; } );,其中lambda的实际调用签名是bool(long),但我将它存储在function&lt;void(int)&gt;中。我也可以做function&lt;void(int)&gt; f2( [](auto...) { return 11; } ),它在std::function 的相同类型中存储一个完全不同的lambda。 OP 想要找出 lambda 的 实际 调用签名,而不是可能能够存储 lambda 的 std::function 的调用签名,但不一定相同。
      • @ZviDan 并且,要将其存储在具有类似调用签名的std::function 中,您必须已经先验知道 lambda 的调用签名是什么(或在至少,它与std::function 类型兼容)。但是,如果您已经知道它,那么您不需要找出它(这是 OP 所要求的)。
      • @JonathanWakely 好的,但是你能解释一下事实的意义吗,如果 2 个(不同的)lambda 在en.cppreference.com/w/cpp/utility/functional/function/… 中的示例意义上具有相同的类型名称?这种等价(如果发生的话)是什么意思,在这种情况下 2 个 lambdas 在什么意义上是相似的?
      猜你喜欢
      • 2018-08-06
      • 2017-06-28
      • 2012-07-22
      • 1970-01-01
      • 2010-10-27
      • 2018-03-02
      • 2011-12-18
      相关资源
      最近更新 更多