如何断言C++类型可流式输出?to_s函数编译报错问题
未实现流式输出类型的
to_s函数编译错误解决方法 问题背景
你编写了用于生成对象字符串表示的代码,以及判断类型是否可流式输出的工具,但传入未实现operator<<的类型实例时,调用to_s仍会触发编译错误:
字符串转换代码
template <typename T> std::string streamable_to_s(const T& obj) { std::stringstream ss; ss << obj; return ss.str(); } template <typename T> std::string non_streamable_to_s(const T& obj) { std::stringstream ss; ss << &obj; std::string type_name = typeid(obj).name(); std::string addr = ss.str(); return "<" + type_name + " " + addr + ">"; } template <typename T> std::string to_s(const T& obj) { return is_streamable<T>() ? streamable_to_s(obj) : non_streamable_to_s(obj); }
可流式输出判断工具
template<typename S, typename T> class _streamable { template<typename SS, typename TT> static auto test(int) -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); template<typename, typename> static auto test(...) -> std::false_type; public: static const bool value = decltype(test<S, T>(0))::value; }; template <typename T> bool is_streamable() { return _streamable<std::stringstream, T>::value; }
问题根源
问题出在C++三元运算符?:的特性上:它要求两个分支的表达式在编译期都必须合法,哪怕运行时只会执行其中一个。所以哪怕is_streamable<T>()返回false,编译器还是会尝试实例化streamable_to_s(obj),这就导致未实现operator<<的类型触发编译错误。
解决方案
需要用编译期分支选择替代运行时的三元运算符,以下两种方案任选:
方案1:C++17及以上——使用if constexpr
if constexpr会在编译期判断条件,只实例化符合条件的分支,避免无效分支的编译检查:
#include <string> #include <sstream> #include <type_traits> #include <typeinfo> // 可流式输出的字符串转换 template <typename T> std::string streamable_to_s(const T& obj) { std::stringstream ss; ss << obj; return ss.str(); } // 不可流式输出的字符串转换 template <typename T> std::string non_streamable_to_s(const T& obj) { std::stringstream ss; ss << &obj; std::string type_name = typeid(obj).name(); std::string addr = ss.str(); return "<" + type_name + " " + addr + ">"; } // 类型可流式输出的判断工具 template<typename S, typename T> class _streamable { template<typename SS, typename TT> static auto test(int) -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); template<typename, typename> static auto test(...) -> std::false_type; public: static const bool value = decltype(test<S, T>(0))::value; }; template <typename T> constexpr bool is_streamable() { return _streamable<std::stringstream, T>::value; } // 修改后的to_s函数 template <typename T> std::string to_s(const T& obj) { if constexpr (is_streamable<T>()) { return streamable_to_s(obj); } else { return non_streamable_to_s(obj); } }
方案2:C11/C14——使用SFINAE重载
通过SFINAE(替换失败不是错误)特性,为可流式和不可流式类型分别定义to_s重载,编译器会自动匹配合适的版本:
#include <string> #include <sstream> #include <type_traits> #include <typeinfo> // 可流式输出的字符串转换 template <typename T> std::string streamable_to_s(const T& obj) { std::stringstream ss; ss << obj; return ss.str(); } // 不可流式输出的字符串转换 template <typename T> std::string non_streamable_to_s(const T& obj) { std::stringstream ss; ss << &obj; std::string type_name = typeid(obj).name(); std::string addr = ss.str(); return "<" + type_name + " " + addr + ">"; } // 类型可流式输出的判断工具 template<typename S, typename T> class _streamable { template<typename SS, typename TT> static auto test(int) -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); template<typename, typename> static auto test(...) -> std::false_type; public: static const bool value = decltype(test<S, T>(0))::value; }; template <typename T> constexpr bool is_streamable() { return _streamable<std::stringstream, T>::value; } // 重载1:处理可流式输出的类型 template <typename T> typename std::enable_if<is_streamable<T>(), std::string>::type to_s(const T& obj) { return streamable_to_s(obj); } // 重载2:处理不可流式输出的类型 template <typename T> typename std::enable_if<!is_streamable<T>(), std::string>::type to_s(const T& obj) { return non_streamable_to_s(obj); }
可选:添加编译期断言验证
如果想确保streamable_to_s只被可流式类型调用,可以在函数开头添加静态断言,让编译错误更直观:
template <typename T> std::string streamable_to_s(const T& obj) { static_assert(is_streamable<T>(), "类型T必须支持std::stringstream的operator<<运算符"); std::stringstream ss; ss << obj; return ss.str(); }
内容的提问来源于stack exchange,提问作者Luan Truong
相关产品推荐
相关产品推荐

