C++11/1Y Lambda 函数的类型签名是什么?

what is the type signature of a c++11/1y lambda function?

提问人:thor 提问时间:2/9/2014 更新时间:3/21/2018 访问量:18743

问:

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

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

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

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

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

C++ C++11 Lambda

评论


答:

18赞 Aluan Haddad 2/9/2014 #1

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

尝试

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

请注意,在诸如此类的非歧义方案中可以省略 。-> 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。

评论

0赞 Jonathan Wakely 2/9/2014
A 具有固定的呼叫签名(例如 在您的示例中),而通用 lambda 的闭包类型可能接受不同的参数,例如 ),因此,尽管您可以将其存储在std::functionint(int,int)[](auto... x) { }std::function
1赞 Aluan Haddad 2/9/2014
我没有在即将到来的标准中考虑通用 lambda。但是,我使用 std::function 实例将闭包传递给其他函数。但实际上,当他们计划添加模板化变量声明时,在下一个标准中,应该能够编写:template<class T> std::function< T (T, T) > f = (auto x,auto y) {return x+y;};
1赞 Jonathan Wakely 2/9/2014
有趣的是,我没有想过使用这样的变量模板。
0赞 Aluan Haddad 2/9/2014
是的,这是一个奇怪的新功能。特别是因为 auto 关键字仅在用作参数类型时才遵循模板的类型推断规则,而不是自动变量声明的类型推断规则。
4赞 Ben Voigt 2/9/2014 #2

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

评论

0赞 Aluan Haddad 2/9/2014
我想你的意思是C++14。我不确定新模板是否意味着 lambda 在结构上是平等的。不过,我可能已经过时了。operator()()
2赞 Ben Voigt 2/9/2014
@Aluan:在进行批准投票之前,没有“C++14”。C++1y 是预期成为 C++14 的草案。最终投票仍可能做出一些改变。
1赞 Jonathan Wakely 2/9/2014 #3

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

不,没有。

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

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

如果需要已知类型,请编写函数对象类型。

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

不。

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

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

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

但是,有没有办法捕捉到它们在类型签名方面相同的事实?

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

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

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

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

评论

0赞 user2485710 2/10/2014
“不,没有语言功能允许这样做”,这相当于说,至少C++1y的多态lambda不能模板化,我不确定这是否正确,我想我之前见过模板化的lambdas与多态lambdas的新C++1y功能有关。
0赞 ZviDan 6/16/2021
“不,没有语言功能允许这样做” 但是在 en.cppreference.com/w/cpp/utility/functional/function/ 的示例中我们看到 lambda 函数的类型在 std::function 中封装时被编码为名称。假设这样的名称是唯一的,这似乎是一种查看 2 种类型是否相同的方法。这是对的吗?
0赞 Jonathan Wakely 6/17/2021
@ZviDan,没有。因为我可以做 lambda 的实际调用签名在哪里,但我将其存储在 .我还可以在同一类型的 .OP 想要找出 lambda 的实际调用签名,而不是可能能够存储 lambda 但不一定相同的调用签名。std::function<void(int)> f( [](long) { return true; } );bool(long)function<void(int)>function<void(int)> f2( [](auto...) { return 11; } )std::functionstd::function
0赞 Jonathan Wakely 6/17/2021
@ZviDan,而且,要将其存储在具有类似调用签名的 a 中,您必须先验地知道 lambda 的调用签名是什么(或者至少,它与类型兼容)。但如果你已经知道了,那么你就不需要找出来(这是OP所要求的)。std::functionstd::function
0赞 ZviDan 6/17/2021
@JonathanWakely好的,但是您能解释一下事实的意义吗,如果 2 个(不同的)lambda 在 en.cppreference.com/w/cpp/utility/functional/function/ 中的示例意义上具有相同的类型名称......?这种等价性(如果发生)意味着什么,在这种情况下,2 个 lambda 在什么意义上是相似的?
17赞 thor 2/10/2014 #4

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

I. 有了这个抽象类型,实际上可以有另一种方法来表达 lambda 的类型签名,如下所示。注意:它不是 lambda 的真实类型,而是 lambda 类型签名的函数摘要。然而,它可能比真正的 lambda 类型更有用,因为每个 lambda 都是它自己的类型autofunction_traits<..>::f_typef_type

如下代码所示,就像一个人可以使用一样,也可以做,这是神秘的另一种选择。当然,这种相似性只是形式上的。下面的代码涉及将 lambda 转换为 a,在构造对象时需要类型擦除的成本,以及通过对象进行间接调用的少量成本。但是,撇开这些实现问题不谈(我不认为这是根本性的,应该永远存在),毕竟,显式表达任何给定 lambda 的(抽象)类型签名是可能的。vector<int>::iterator_type i = v.begin()function_traits<lambda>::f_type f = lambdaautostd::functionstd::functionstd::functionstd::function

II. 也可以编写一个包装器(非常类似于 和 )来自动将 lambda(和其他可调用对象,如函数指针/函子)转换为 ,具有相同的类型演绎功能。make_functionstd::make_pairstd::make_tuplefstd::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;
}