C++类型推导引发重载函数调用歧义:Lambda相关问题问询
核心问题:Lambda的类型不是std::function
当你用auto f = [](int const& x) -> int { return 3*x; };定义Lambda时,f的类型不是std::function<int(int const&)>,而是编译器为该Lambda生成的匿名闭包类型。auto会推导这个闭包的真实类型,不会自动将其包装为std::function。
为什么会出现重载歧义?
你的Test::apply_and_increment有两个重载,分别接受std::function<int(int const&)>和std::function<int(int&)>。问题在于:
- 你的Lambda(以及
case_0函数)的调用签名可以兼容两种std::function类型:- 对于
std::function<int(int const&)>:Lambda的参数是const int&,完全匹配,可直接构造该std::function。 - 对于
std::function<int(int&)>:当这个std::function被调用时,会把int&类型的实参(即Test::n)绑定到Lambda的const int&形参上——这是C++允许的(非const左值可以绑定到const引用),因此该std::function也能成功构造。
- 对于
因为两种重载都能通过隐式转换匹配,编译器无法确定你要调用哪一个,所以报歧义错误。
而case_1函数的签名是int(int&),它只能转换为std::function<int(int&)>:如果尝试转成std::function<int(int const&)>,调用时需要把const int&实参传给int&形参,这是C++禁止的(不能把const引用绑定到非const引用),因此只有一个重载可行,没有歧义。
解决方法
方法1:显式转换为目标std::function类型
和处理case_0的方式一致,将Lambda显式转换为对应的std::function类型:
t.apply_and_increment(std::function<int(int const&)>(f));
方法2:用模板代替std::function参数,让推导更精确
修改Test类的成员函数为模板,直接接受可调用对象,避免std::function的隐式转换歧义(基于C++20概念):
#include <functional> #include <concepts> #include <cstdlib> // ... 其他代码保持不变 ... class Test { public: Test(int const n) : n(n) {} template<typename F> requires std::invocable<F, int const&> int apply_and_increment(F f) { n++; return f(n); } template<typename F> requires std::invocable<F, int&> && !std::invocable<F, int const&> int apply_and_increment(F f) { return f(n); } private: int n; };
这里用std::invocable概念约束:第一个模板接受能被const int&调用的对象,第二个只接受能被int&调用但不能被const int&调用的对象,实现精确匹配。
方法3:使用标签分发(兼容C++17及更早版本)
如果无法使用C++20概念,可通过标签分发区分两种情况:
#include <functional> #include <type_traits> #include <cstdlib> // ... 其他代码保持不变 ... class Test { private: int n; template<typename F> int apply_impl(F f, std::true_type /* is_const_ref */) { n++; return f(n); } template<typename F> int apply_impl(F f, std::false_type /* is_const_ref */) { return f(n); } public: Test(int const n) : n(n) {} template<typename F> int apply_and_increment(F f) { if constexpr (std::is_invocable_v<F, int const&> && !std::is_invocable_v<F, int&>) { return apply_impl(f, std::true_type{}); } else if constexpr (std::is_invocable_v<F, int&>) { return apply_impl(f, std::false_type{}); } else { static_assert(false, "Invalid callable type"); } } };
内容的提问来源于stack exchange,提问作者gkaf

