C++14中如何按优先级调用toString:成员函数>自由函数>工具函数
问题解决:按优先级调用toString函数
问题背景
现有四个结构体,各自的toString定义情况如下:
foo:包含成员函数toString()bar:仅存在全局自由函数toString(const bar&)baz:同时拥有成员函数与全局自由函数toStringblarf:两者皆无
需要实现一个统一的str::toString函数,按以下优先级调用:
- 优先调用结构体的成员函数
toString() - 若无成员函数,调用全局自由函数
toString(const T&) - 若两者皆无,调用工具函数
toString_()生成默认字符串
现有结构体代码:
struct foo final { std::string toString() const { return "foo"; } }; struct bar final { }; std::string toString(const bar&) { return "<bar>"; } struct baz final { std::string toString() const { return "baz"; } }; std::string toString(const baz& b) { return "<" + b.toString() + ">"; } struct blarf final {};
尝试的实现代码
尝试通过SFINAE实现优先级调用,但编译报错:
namespace details { template<typename T> inline std::string toString_(const T& t) { const void* pV = &t; return "#" + std::to_string(reinterpret_cast<size_t>(pV)); } template<typename T> inline auto toString_imp(const T& obj, int) -> decltype(obj.toString(), std::string()) { return obj.toString(); } template<typename T> inline auto toString_imp(const T& obj, long) -> decltype(toString(obj), std::string()) { return toString(obj); } template<typename T> inline auto toString_imp(const T& obj, long long) -> decltype(toString_(obj), std::string()) { return toString_(obj); } template<typename T> inline auto toString(const T& obj) -> decltype(toString_imp(obj, 0), std::string()) { return toString_imp(obj, 0); } } namespace str { template<typename T> std::string toString(const T& t) { return details::toString(t); } }
测试代码:
int main() { const foo foo; std::cout << str::toString(foo) << "\n"; // 期望输出 "foo" const bar bar; std::cout << str::toString(bar) << "\n"; // 期望输出 "<bar>" const baz baz; std::cout << str::toString(baz) << "\n"; // 期望输出 "baz" const blarf blarf; std::cout << str::toString(blarf) << "\n"; // 期望输出类似 "#31415926539" 的字符串 }
编译错误信息
1> error C2672: 'details::toString': no matching overloaded function found 1> message : could be 'unknown-type details::toString(const T &)' 1> message : Failed to specialize function template 'unknown-type details::toString(const T &)' 1> message : see declaration of 'details::toString' 1> message : With the following template arguments: 1> message : 'T=T' 1> message : see reference to function template instantiation 'std::string str::toString<bar>(const T &)' being compiled 1> with 1> [ 1> T=bar 1> ]
问题分析与修复
错误原因
在details::toString_imp的第二个重载中,decltype(toString(obj))会触发参数依赖查找(ADL),此时编译器会同时查找全局命名空间和details命名空间中的toString函数。而details命名空间中恰好有一个toString模板函数,其返回类型又依赖于toString_imp的调用,形成循环依赖,导致编译器无法推导返回类型,最终报错。
修复代码
修改第二个toString_imp重载,明确指定调用全局命名空间的toString函数,避免ADL查找到details内部的模板:
namespace details { template<typename T> inline std::string toString_(const T& t) { const void* pV = &t; return "#" + std::to_string(reinterpret_cast<size_t>(pV)); } // 优先匹配成员函数(int参数比long更匹配字面量0) template<typename T> inline auto toString_imp(const T& obj, int) -> decltype(obj.toString(), std::string()) { return obj.toString(); } // 明确调用全局命名空间的toString,避免ADL冲突 template<typename T> inline auto toString_imp(const T& obj, long) -> decltype(::toString(obj), std::string()) { return ::toString(obj); } // 兜底调用工具函数 template<typename T> inline auto toString_imp(const T& obj, long long) -> decltype(toString_(obj), std::string()) { return toString_(obj); } template<typename T> inline auto toString(const T& obj) -> decltype(toString_imp(obj, 0)) { return toString_imp(obj, 0); } } namespace str { template<typename T> std::string toString(const T& t) { return details::toString(t); } }
修复说明
- 优先级保证:通过参数类型
int/long/long long的重载决议,字面量0会优先匹配int参数的重载(成员函数检测),其次是long(全局函数检测),最后是long long(兜底工具函数),符合需求的优先级顺序。 - 避免ADL冲突:使用
::toString(obj)明确指定全局命名空间的函数,防止编译器误查找到details内部的toString模板,打破循环依赖。 - 返回类型简化:
details::toString的返回类型直接推导为toString_imp(obj,0)的类型,无需额外拼接std::string(),更简洁。
测试验证后,四个结构体的调用均符合预期:
foo:调用成员函数,输出foobar:调用全局函数,输出<bar>baz:优先调用成员函数,输出bazblarf:调用兜底工具函数,输出带地址的字符串
内容的提问来源于stack exchange,提问作者Pam Patrick
相关产品推荐
相关产品推荐

