C++中传递重载函数作为参数时如何指定目标重载版本?
针对标准库算法传入重载函数时的编译失败问题,以下两种方案均比手动写指定参数类型的lambda、强制转换函数指针更简洁,且能让编译器自动完成重载匹配,不需要手动声明复杂的返回值/参数类型。
零前置依赖的即时可用写法(C++14及以上)
不需要提前定义任何工具,直接使用泛型转发lambda即可,编译器会根据算法实际传入的参数类型自动匹配对应重载:
std::vector<std::string> strings{"1","2","3"}; std::vector<double> doubles(3); std::transform(begin(strings), end(strings), begin(doubles), [](auto&& s) { return std::stod(std::forward<decltype(s)>(s)); } );
如果调用的函数不需要处理右值转发优化(比如std::stod本身就接收const引用参数),还可以进一步简化成[](auto&& s) { return std::stod(s); },不需要手动写任何参数、返回值类型,哪怕目标函数返回值是嵌套多层的复杂模板类型,也能自动适配。
一次定义全局复用的通用方案(C14及以上,C11可通过模板结构体实现同等效果)
如果频繁遇到需要传递重载函数的场景,可以在项目公共头文件里定义一次通用的重载提升宏,之后所有重载函数都可以直接套用,写法和直接传函数名几乎一样简洁:
// 全局仅需定义一次 #define FWD(x) std::forward<decltype(x)>(x) #define LIFT(func) [](auto&&... args) noexcept(noexcept(func(FWD(args)...))) -> decltype(auto) { return func(FWD(args)...); }
回到最开始的示例代码,直接套宏即可通过编译,不需要做任何额外的类型声明:
std::transform(begin(strings), end(strings), begin(doubles), LIFT(std::stod));
这个方案的核心原理是把原本无法直接传参的重载函数集,包装成一个泛型可调用对象:编译器传递参数时只需要识别lambda这个函数对象类型,不需要在传参阶段就确定要选哪个重载;等到算法实际执行、传入具体类型的实参时,才会在lambda内部触发重载解析,自动匹配参数类型完全对应的重载版本。
该方案没有任何额外运行时开销,编译优化后和直接调用原函数的性能完全一致,且适配所有重载场景,不管目标函数有多少个重载版本、返回值类型多复杂,都能正确匹配。
注意:目前C标准(截至C23)没有提供原生的重载集提升工具,上述
LIFT写法是工业界C++项目中解决这类问题的通用惯用法,不存在兼容性问题,也不会引入额外运行时开销。
内容的提问来源于stack exchange,提问作者bitmask

