C++重载函数调用歧义求助:std::function子类化后的重载问题
问题:std::function子类化后重载函数调用歧义
我在研究C++的std::function对象时,想要为其添加一些功能,因此对它进行了子类化处理。然而,当我尝试编写一个基于传入函数签名进行重载的doThing函数时,编译器提示函数调用存在歧义。请帮忙解释歧义产生的原因,以及我该如何消除这种歧义?
示例代码
#include <functional> #include <iostream> class A0 : public std::function<void()> { public: template <typename T> A0(const T& t) : std::function<void()> { t } { } }; template <typename T1> class A1 : public std::function<void(T1)> { public: template <typename T> A1(const T& t) : std::function<void(T1)> { t } { } }; void doThing(const A0& a0) { std::cout << "Do A0 thing\n"; } void doThing(const A1<int>& a1) { std::cout << "Do A1 thing\n"; } int main() { doThing([] (int i) {}); return 0; }
编译器错误输出
g++ .\sources\Sample.cpp .\sources\Sample.cpp: In function 'int main()': .\sources\Sample.cpp:37:12: error: call of overloaded 'doThing(main()::<lambda(int)>)' is ambiguous 37 | doThing([] (int i) {}); | ~~~~~~~^~~~~~~~~~~~~~~ .\sources\Sample.cpp:25:6: note: candidate: 'void doThing(const A0&)' 25 | void doThing(const A0& a0) | ^~~~~~~ .\sources\Sample.cpp:30:6: note: candidate: 'void doThing(const A1<int>&)' 30 | void doThing(const A1<int>& a1) | ^~~~~~~
歧义产生的原因
- 构造函数的通用性:
A0和A1<int>都包含模板构造函数,能接受任意类型的参数,随后尝试将其转换为对应的std::function基类类型。 - std::function的宽松转换规则:
std::function<void()>允许接收带多余参数的可调用对象——调用时会自动忽略多余的参数。因此,你的带int参数的lambda既可以被转换为std::function<void()>(进而构造A0),也可以被转换为std::function<void(int)>(进而构造A1<int>)。 - 转换优先级无差异:这两个转换都属于用户定义的转换序列,编译器无法判定哪个更符合预期,因此报出歧义错误。
消除歧义的方法
方法1:限制构造函数的转换范围
通过SFINAE技术,让A0和A1的构造函数只接受能精确匹配目标std::function签名的可调用对象,阻断不必要的转换路径:
#include <functional> #include <iostream> #include <type_traits> class A0 : public std::function<void()> { public: // 仅允许能转换为std::function<void()>的参数 template <typename T, typename = std::enable_if_t<std::is_convertible_v<T, std::function<void()>>>> A0(const T& t) : std::function<void()>{t} { } }; template <typename T1> class A1 : public std::function<void(T1)> { public: // 仅允许能转换为std::function<void(T1)>的参数 template <typename T, typename = std::enable_if_t<std::is_convertible_v<T, std::function<void(T1)>>>> A1(const T& t) : std::function<void(T1)>{t} { } }; void doThing(const A0& a0) { std::cout << "Do A0 thing\n"; } void doThing(const A1<int>& a1) { std::cout << "Do A1 thing\n"; } int main() { doThing([] (int i) {}); // 仅匹配A1<int>重载 doThing([] () {}); // 仅匹配A0重载 return 0; }
方法2:显式构造目标类型
如果不想修改A0和A1的定义,可以在调用doThing时显式指定要构造的对象类型,跳过编译器的自动转换选择:
int main() { doThing(A1<int>{[] (int i) {}}); // 明确指定构造A1<int> return 0; }
方法3:使用组合而非继承(推荐)
std::function没有虚析构函数,子类化它存在风险(比如通过基类指针删除子类对象会导致未定义行为)。更安全的方式是把std::function作为成员变量,通过组合来扩展功能:
#include <functional> #include <iostream> #include <type_traits> class A0 { private: std::function<void()> func; public: template <typename T, typename = std::enable_if_t<std::is_convertible_v<T, std::function<void()>>>> A0(const T& t) : func(t) {} // 保留std::function的调用行为 void operator()() const { func(); } // 在这里添加你的扩展功能 }; template <typename T1> class A1 { private: std::function<void(T1)> func; public: template <typename T, typename = std::enable_if_t<std::is_convertible_v<T, std::function<void(T1)>>>> A1(const T& t) : func(t) {} void operator()(T1 arg) const { func(arg); } // 添加你的扩展功能 }; void doThing(const A0& a0) { std::cout << "Do A0 thing\n"; } void doThing(const A1<int>& a1) { std::cout << "Do A1 thing\n"; } int main() { doThing([] (int i) {}); doThing([] () {}); return 0; }
内容的提问来源于stack exchange,提问作者danschultewhpc
相关产品推荐
相关产品推荐

