为什么重载的std::function构造参数传入无参绑定时会出现歧义?
编译失败原因
- 核心原因是
std::bind返回的不是std::function类型,是编译器内部生成的匿名可调用对象,这类对象的调用运算符支持忽略多余传入参数:你创建的绑定了Bar::baz的无参可调用对象,既可以不加参数调用,也可以传入一个double参数(参数会被直接丢弃),因此它可以同时隐式转换为std::function<void(double)>和std::function<void(void)>两种类型,导致重载决议时两个构造函数的匹配优先级完全相同,触发歧义错误。 - 而第一个带
std::placeholders::_1的绑定对象必须接收一个double参数才能调用,只能匹配std::function<void(double)>的构造函数,所以编译正常。
更简洁的实现方案
方案1:显式指定转换类型(最小改动兼容旧代码)
直接在构造时显式把bind的结果转成对应签名的std::function,明确告诉编译器选哪个重载:
Overloader overloader2(std::function<void(void)>(std::bind(static_cast<void(Bar::*)(void)>(&Bar::baz), &bar)));
方案2:替换std::bind为lambda(最常用写法,可读性更高)
lambda的调用签名是固定的,不会出现多余参数兼容的问题,写法也比bind更简洁直观:
// 带参数的构造也可以统一换成lambda写法 Overloader overloader1([&foo](double val) { foo.baz(val); }); // 无参构造无歧义 Overloader overloader2([&bar]() { bar.baz(); });
方案3:用SFINAE约束构造函数(彻底避免后续使用的歧义)
如果你使用C++17及以上版本,可以给Overloader加模板构造函数,自动识别传入可调用对象的签名,不需要用户手动处理类型:
#include <type_traits> #include <functional> class Overloader { public: // 匹配可接收double参数的可调用对象 template <typename Func, std::enable_if_t<std::is_invocable_v<Func, double>, int> = 0> Overloader(Func&& inFunc) : funcWithArg(std::forward<Func>(inFunc)) {} // 匹配无参可调用对象,排除同时支持双签名的对象 template <typename Func, std::enable_if_t<std::is_invocable_v<Func> && !std::is_invocable_v<Func, double>, int> = 0> Overloader(Func&& inFunc) : funcNoArg(std::forward<Func>(inFunc)) {} private: std::function<void(double)> funcWithArg; std::function<void(void)> funcNoArg; };
改造后直接传bind或者lambda都不需要额外处理类型,编译器会自动匹配正确的构造函数。
内容的提问来源于stack exchange,提问作者Archduke
相关产品推荐
相关产品推荐

