Lambda被转为bool而非推导函数指针类型,operator<<重载编译异常
我完全理解你的需求:想要重载operator<<,让它接收Lambda(或其他可调用对象)并传递给指定函数,由该函数用默认构造的参数列表调用这个对象,而非直接调用Lambda本身。但现在编译器总把Lambda隐式转成bool走默认重载,自己写的重载还编译失败,这确实挺头疼的。
问题根源
从C++11开始,Lambda的闭包类型自带隐式的operator bool() const noexcept转换运算符(用来支持在条件判断中使用Lambda)。当你的自定义operator<<重载匹配优先级低于默认的operator<<(bool)时,编译器就会选择把Lambda转成bool的路径。
另外,Lambda是匿名类型,普通非模板重载无法直接匹配,这也是你自己写的函数编译失败的核心原因之一。
解决方案
我们可以通过模板重载+SFINAE实现匹配度更高的operator<<,专门针对可调用对象,同时避免和默认重载冲突。以下是具体实现步骤:
步骤1:定义调用可调用对象的目标函数
假设你的目标函数是invoke_with_default_args,负责用默认参数列表调用传入的可调用对象:
#include <iostream> #include <type_traits> // 示例目标函数:用默认参数调用可调用对象 template<typename Callable> auto invoke_with_default_args(Callable&& func) -> decltype(func()) { return func(); // 这里执行无参调用,符合你的"默认构造参数列表"需求 }
步骤2:重载operator<<,匹配可调用对象
我们用类型特性限制模板只接受可无参调用的类型,让这个重载的匹配优先级高于默认的bool重载。考虑到你用的是gcc7.3.1(对C++17特性支持有限),这里提供两种写法:
写法1:用C++14兼容的std::result_of
// 针对可无参调用对象的operator<<重载 template<typename Callable, typename = typename std::result_of<Callable()>::type> std::ostream& operator<<(std::ostream& os, Callable&& func) { // 将可调用对象传递给目标函数,由它完成调用并返回结果 auto result = invoke_with_default_args(std::forward<Callable>(func)); os << result; // 输出调用结果 return os; }
写法2:用experimental版本的is_invocable(gcc7.3.1支持)
如果想更直观地判断可调用性,可以用<experimental/type_traits>里的特性:
#include <experimental/type_traits> template<typename Callable, typename = std::enable_if_t<std::experimental::is_invocable_v<Callable>>> std::ostream& operator<<(std::ostream& os, Callable&& func) { auto result = invoke_with_default_args(std::forward<Callable>(func)); os << result; return os; }
步骤3:测试代码
现在编译器会正确匹配我们的重载,不会再把Lambda转成bool:
int main() { // 返回字符串的无参Lambda auto str_lambda = []() { return "Hello from Lambda!"; }; std::cout << str_lambda << std::endl; // 输出:Hello from Lambda! // 返回整数的无参Lambda auto int_lambda = []() { return 42; }; std::cout << int_lambda << std::endl; // 输出:42 return 0; }
方案生效的原因
- 更高的匹配优先级:模板重载会直接匹配Lambda的匿名类型,而默认的
operator<<(bool)需要隐式转换,因此编译器会优先选择我们的自定义重载。 - 类型安全限制:SFINAE确保这个模板只接受可无参调用的类型,避免和其他类型的
operator<<重载冲突。 - 符合需求的调用逻辑:我们没有直接调用Lambda,而是把它传递给目标函数完成调用,完全贴合你的需求。
扩展说明
如果你的目标函数需要带参数,只需修改类型特性的参数即可。比如要匹配接受int和std::string的可调用对象,就把std::result_of<Callable()>改成std::result_of<Callable(int, std::string)>,或者调整is_invocable的模板参数。
内容的提问来源于stack exchange,提问作者Tarirah

