C++11中std::function::target针对lambda的模板类型疑问
我想把lambda表达式存入std::function对象,再获取它的底层函数指针。出于我无法理解的原因,在C++11环境下,function::target()是模板函数,传入错误类型会返回nullptr。
我有以下三个问题:
- 应该用什么模板类型才能正确获取lambda的底层指针?
- 既然
std::function本身已是模板类,为什么function::target()还需要模板参数? - lambda函数的类型和全局函数不同,为什么二者都能存入同一个
std::function对象?
我对lambda的认知比较模糊,以为lambda会生成临时函数、绑定到命名空间,类型取决于所在命名空间,但不确定这个认知对不对,也不知道怎么把它用到模板参数里,希望了解背后的C++机制。
代码示例
#include <iostream> #include <functional> using namespace std; void foo(int) { std::cout << "foo" << std::endl; } int main() { std::function<void(int)> a(foo); // 合法的全局函数 std::function<void(int)> b([](int) -> void {}); // 临时lambda表达式 auto bar = [](int) -> void {}; // 存储在变量中的lambda表达式 std::function<void(int)> c(bar); std::cout << a.target<void(*)(int)>() << std::endl; // 输出类似0x7ffe8e74ecf0 std::cout << b.target<void(*)(int)>() << std::endl; // 输出0 std::cout << c.target<void(*)(int)>() << std::endl; // 输出0 std::cout << &bar << std::endl; // 输出类似0x7ffe8e74ecef return 0; }
问题解答
1. 正确获取lambda底层指针的模板类型
首先明确:无捕获lambda可隐式转换为函数指针,但它本身的类型不是函数指针。lambda的本质是编译器生成的独一无二的匿名闭包类,这个类重载了operator()。
要获取lambda的底层指针,你需要传入这个闭包类型作为target()的模板参数。但闭包类型是匿名的,无法直接写出,可通过decltype推导:
// 针对代码中的bar auto bar_ptr = c.target<decltype(bar)>(); if (bar_ptr) { std::cout << bar_ptr << std::endl; // 输出地址与&bar一致 }
如果只需要函数指针而非闭包对象指针,可以先把lambda显式转换为函数指针再存入std::function:
std::function<void(int)> b(static_cast<void(*)(int)>([](int) -> void {})); // 此时target<void(*)(int)>()会返回非空指针
注意:临时lambda(如代码中的b)的闭包类型无法提前用decltype获取,必须绑定到变量才能推导类型。
2. 为什么std::function::target()需要模板参数
std::function<R(Args...)>是类型擦除容器:它能存储任何符合调用签名的可调用对象(函数指针、lambda、仿函数、绑定表达式等),但本身只知道对象符合签名,不知道其具体类型。
target<T>()的作用是尝试将内部存储的对象转换为T*类型——只有当内部对象确实是T类型时才返回有效指针,否则返回nullptr。由于std::function通过类型擦除丢失了对象的具体类型信息,必须通过模板参数明确指定要匹配的类型。
3. 不同类型的可调用对象为何能存入同一个std::function
这靠类型擦除机制实现:
- 当你传入可调用对象时,
std::function会创建一个内部包装器,保存对象的副本或引用。 - 包装器实现了统一的虚函数接口,包含调用对象的方法。
- 调用
std::function的operator()时,会通过这个虚接口调用内部存储的对象。
只要可调用对象符合std::function的签名要求,就能被包装器适配,从而存入同一个std::function对象。
补充:lambda的正确认知
lambda并非绑定到命名空间的临时函数,而是编译器生成的闭包类实例:
- 每个lambda对应一个独一无二的匿名类,类中包含重载的
operator()(mutable修饰时,operator()为非const)。 auto bar = [](){};中,bar是该匿名类的实例对象,而非函数指针。- 无捕获lambda可隐式转换为对应函数指针(C++标准特殊规则),但有捕获lambda不行——捕获的变量需要存在闭包对象中,无法用普通函数指针表示。
- lambda的类型与所在命名空间无关,是编译器在当前作用域生成的局部匿名类型。
内容的提问来源于stack exchange,提问作者Daniel Handojo

