向泛型函数传递不同签名Lambda表达式的实现问题
如何编写可接收不同签名Lambda的泛型函数
嘿,你这个需求在C++里其实有不少灵活的实现方案,我结合你的示例代码给你梳理几种常用的方式:
1. 利用模板参数自动推导(最直接的方案)
你给出的示例代码其实已经是这种思路的典型实现啦!因为Lambda表达式是匿名的独特类型,模板函数会为每个不同签名的Lambda自动生成对应的实例,完美适配不同的调用签名。
不过我们可以稍微优化一下代码的可读性,比如用std::invocable概念(C++20)来做编译期检查,确保传入的对象确实是可调用的:
#include <iostream> #include <concepts> template <typename Callable> requires std::invocable<Callable> || std::invocable<Callable, int> void foo(Callable func, int* i) { if (i) { std::invoke(func, *i); // 用std::invoke更通用,支持更多可调用对象 } else { std::invoke(func); } } int main() { auto n = new int{2}; foo([](int x) { std::cout << x << '\n'; }, n); foo([]() { std::cout << "Foo" << '\n'; }, nullptr); // 如果传入不符合签名的Lambda,编译会直接报错,更友好 // foo([](std::string s) {}, n); // 编译错误:不满足requires约束 delete n; // 别忘了释放内存! return 0; }
优点:
- 零开销,编译期生成代码,运行效率最高
- 写法简单,自动适配所有符合调用要求的可调用对象(Lambda、函数指针、仿函数等)
- C++20的
requires约束能提前拦截错误,编译信息更友好
2. 使用std::variant统一处理多种签名(C++17+)
如果你需要把不同签名的Lambda统一存储或者在一个函数里集中处理,可以用std::variant包装不同的可调用类型,再配合std::visit来调用:
#include <iostream> #include <variant> #include <functional> // 定义可接收的两种签名类型 using CallableVariant = std::variant<std::function<void()>, std::function<void(int)>>; void foo(CallableVariant func, int* i) { std::visit([i](auto&& f) { if (i) { // 尝试用int参数调用,如果签名不匹配会触发编译错误 if constexpr (std::invocable<decltype(f), int>) { f(*i); } else { std::cout << "Warning: Can't pass int to this callable\n"; f(); } } else { f(); } }, func); } int main() { auto n = new int{2}; foo(std::function<void(int)>([](int x) { std::cout << x << '\n'; }), n); foo(std::function<void()>([]() { std::cout << "Foo" << '\n'; }), nullptr); delete n; return 0; }
优点:
- 可以统一管理多种不同签名的可调用对象,适合需要存储或批量处理的场景
- 用
if constexpr做编译期分支,避免运行时开销
缺点:
- 需要提前明确所有支持的签名类型,灵活性不如模板方案
- 包装成
std::function会有微小的运行时开销(虚函数调用)
3. 用重载函数配合模板(传统方案)
如果你不想用C++17/20的新特性,也可以通过重载模板函数来实现:
#include <iostream> // 重载版本1:接收无参可调用对象 template <typename Callable> void foo(Callable func, std::nullptr_t) { func(); } // 重载版本2:接收带int参数的可调用对象 template <typename Callable> void foo(Callable func, int* i) { if (i) { func(*i); } else { func(); // 也可以在这里处理无参情况,或者调用无参重载 } } int main() { auto n = new int{2}; foo([](int x) { std::cout << x << '\n'; }, n); foo([]() { std::cout << "Foo" << '\n'; }, nullptr); delete n; return 0; }
这种方式兼容性最好,C++11及以上就能用,逻辑也很清晰,通过参数类型(nullptr_t vs int*)来匹配对应的重载。
内容的提问来源于stack exchange,提问作者aaamourao
相关产品推荐
相关产品推荐

