将lambda模板类作为另一类成员存储时,如何推导模板参数?
解决方案:在类中存储Lambda模板对象(Invoker)
针对你遇到的问题,核心难点在于Lambda的类型是匿名且唯一的,无法直接在类定义阶段推导并作为模板参数使用;而直接用成员函数指针则缺少对象上下文,无法直接调用。下面提供三种可行的解决思路,覆盖不同的C++版本和性能需求:
方案一:用std::function消除模板依赖(兼容C++11+)
这是最通用且简单的方案,将Invoker改为接受std::function<void()>类型的回调,不管是Lambda、绑定了对象的成员函数还是普通函数都能存储,兼容性拉满。
#include <iostream> #include <functional> // 改造Invoker,不再依赖具体回调类型 struct Invoker { explicit Invoker(std::function<void()> cb) : cb(std::move(cb)) {} std::function<void()> cb; void invoke() { cb(); } }; struct Worker { void some_callback() { std::cout << "callback in worker\n"; } Invoker invoker; // 在构造函数中初始化,用Lambda捕获this绑定上下文 Worker() : invoker([this](){ some_callback(); }) {} }; int main() { Invoker invoker([]{std::cout << "invoker\n";}); invoker.invoke(); Worker worker; worker.invoker.invoke(); return 0; }
优点:
- 代码简洁,无需处理复杂的模板推导
- 兼容C++11及以上所有版本
- 支持任意可调用对象,扩展性强
缺点:
- 存在微小的运行时开销(
std::function内部通过虚函数调用),但绝大多数场景下可以忽略
方案二:将Worker改为模板类(零开销,兼容C++17+)
如果追求极致性能,不想用std::function的虚函数开销,可以把Worker设计为模板类,将Lambda类型作为模板参数,在创建Worker实例时由编译器自动推导。
#include <iostream> template <typename TCallback> struct Invoker { explicit Invoker(TCallback&& cb) : cb(std::move(cb)) {} TCallback cb; void invoke() { cb(); } }; // Worker模板类,回调类型作为模板参数 template <typename TCallback> struct Worker { void some_callback() { std::cout << "callback in worker\n"; } Invoker<TCallback> invoker; // 构造函数接收绑定好上下文的回调 explicit Worker(TCallback cb) : invoker(std::move(cb)) {} }; // 类模板推导指引,让编译器自动推导TCallback类型(C++17+支持) template <typename TCallback> Worker(TCallback) -> Worker<TCallback>; int main() { Invoker invoker([]{std::cout << "invoker\n";}); invoker.invoke(); // 创建Worker时传入捕获this的Lambda Worker worker([](Worker* self) { self->some_callback(); }, &worker); worker.invoker.invoke(); return 0; }
优点:
- 零运行时开销,直接调用原始Lambda
- 完全保留类型信息,适合性能敏感场景
缺点:
- 代码复杂度较高,需要处理模板推导和上下文绑定
- 每个不同的Lambda都会生成不同的
Worker实例,可能增加二进制体积
方案三:C++20特性:非静态成员auto声明(最简洁)
C++20允许非静态成员变量使用auto类型声明,这样可以直接在类内定义Lambda成员,再用decltype获取其类型来实例化Invoker。
#include <iostream> template <typename TCallback> struct Invoker { explicit Invoker(TCallback&& cb) : cb(std::move(cb)) {} TCallback cb; void invoke() { cb(); } }; struct Worker { void some_callback() { std::cout << "callback in worker\n"; } // C++20允许非静态成员用auto声明Lambda auto lambda_in_class = [this]{ some_callback(); }; // 用decltype获取Lambda的类型,实例化Invoker Invoker<decltype(lambda_in_class)> invoker{std::move(lambda_in_class)}; }; int main() { Invoker invoker([]{std::cout << "invoker\n";}); invoker.invoke(); Worker worker; worker.invoker.invoke(); return 0; }
优点:
- 代码最简洁,完全符合你最初的设想
- 零运行时开销
缺点:
- 必须使用支持C++20的编译器(如GCC 8+、Clang 7+、MSVC 2019+)
原代码问题解析
你最初尝试用成员函数指针的写法失败,是因为成员函数指针必须依赖对象实例才能调用。如果一定要用成员函数指针,需要修改Invoker让它持有Worker的指针,比如:
template <typename TClass, typename TCallback> struct Invoker { explicit Invoker(TClass* obj, TCallback cb) : obj(obj), cb(cb) {} TClass* obj; TCallback cb; void invoke() { (obj->*cb)(); // 必须通过对象实例调用成员函数 } }; struct Worker { void some_callback() { std::cout << "callback in worker\n"; } Invoker<Worker, decltype(&Worker::some_callback)> invoker{this, &Worker::some_callback}; };
但这种写法耦合性高,不如前面的方案灵活。
内容的提问来源于stack exchange,提问作者Yuanyi Wu
相关产品推荐
相关产品推荐

