C++17+ STL容器友好调试打印模板实现技术问询
通用STL容器调试打印实现(C++17+)
核心需求
- 支持任意STL容器:所有拥有begin/end迭代器、可用于范围for循环的类型,包括初始化列表,额外支持tuple
- 识别字符串类类型:覆盖
const char*、std::string、std::string_view,同时支持宽字符版本 - 指针打印:保留默认行为(输出内存地址)
预期示例效果
std::vector<std::string> vec = {"first", "second one", "third and final"}; std::cout << vec; // 输出: "[ \"first\", \"second one\", \"third and final\" ]" std::vector<int> other_vec = {1,2,3}; std::cout << other_vec; // 输出: "[ 1, 2, 3 ]"
实现过程中遇到的问题
- 初始版本仅支持
std::vector,未实现字符串自动加引号逻辑 - 尝试函数模板偏特化时触发Clang错误:
Function template partial specialization is not allowed - 改用通用容器模板时与Google Test代码冲突,出现编译错误:
In file included from tinytest_test.cpp:9: ./tinytest.h:77:27: error: member reference type 'const testing::internal::MutexBase *' is a pointer; did you mean to use '->'? for (auto it = container.begin(); it != container.end(); it++) { ~~~~~~~~~^ -> external/com_google_googletest/googletest/include/gtest/internal/gtest-port.h:1717:60: note: in instantiation of function template specialization 'operator<<<char, std::char_traits<char>, const testing::internal::MutexBase *>' requested here << "The current thread is not holding the mutex @" << this; ^ In file included from tinytest_test.cpp:9: ./tinytest.h:77:28: error: no member named 'begin' in 'testing::internal::MutexBase' for (auto it = container.begin(); it != container.end(); it++) { ~~~~~~~~~ ^ ./tinytest.h:77:52: error: member reference type 'const testing::internal::MutexBase *' is a pointer; did you mean to use '->'? for (auto it = container.begin(); it != container.end(); it++) { ~~~~~~~~~^ -> ./tinytest.h:77:53: error: no member named 'end' in 'testing::internal::MutexBase' for (auto it = container.begin(); it != container.end(); it++) { ~~~~~~~~~ ^
- 存在指针误匹配字符串打印重载的问题
解决方案与最终实现
通过移除operator<<重载,改用独立的PrettyPrint系列函数解决冲突问题,同时利用C++17特性实现通用容器和tuple的支持,最终代码如下:
//////////////////////////////////////////////////////////////////////////////// // 辅助模板 - 无需直接测试 template <typename TChar> struct StringTraits; template <> struct StringTraits<char> { static constexpr const char* Literal(const char* narrow, const wchar_t* wide) { return narrow; } }; template <> struct StringTraits<wchar_t> { static constexpr const wchar_t* Literal(const char* narrow, const wchar_t* wide) { return wide; } }; //////////////////////////////////////////////////////////////////////////////// // 前向声明 // const char* 和 const wchar_t* template <typename TChar> std::basic_string<TChar> EscapeForPrinting(const TChar* text); // std::string 和 std::wstring template <typename TChar, typename TTraits> std::basic_string<TChar, TTraits> EscapeForPrinting(const std::basic_string<TChar, TTraits>& text); // std::string_view 和 std::wstring_view template <typename TChar, typename TTraits> std::basic_string<TChar, TTraits> EscapeForPrinting(const std::basic_string_view<TChar, TTraits>& text); // const char* 和 const wchar_t* template <typename TChar, typename TTraits> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const TChar* item); // std::string 和 std::wstring template <typename TChar, typename TTraits> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const std::basic_string<TChar, TTraits>& item); // std::string_view 和 std::wstring_view template <typename TChar, typename TTraits> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const std::basic_string_view<TChar, TTraits>& item); // std::tuple template <typename TChar, typename TTraits, typename... TArgs> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const std::tuple<TArgs...>& tuple); // 容器(所有拥有begin/end迭代器的类型) template <typename TChar, typename TTraits, typename TContainer, typename = std::enable_if_t< std::is_same_v<decltype(std::declval<TContainer>().begin()), decltype(std::declval<TContainer>().end())>>, typename = std::enable_if_t<std::is_base_of_v< std::input_iterator_tag, typename std::iterator_traits<decltype(std::declval<TContainer>().begin())>::iterator_category>>> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, TContainer container); // 兜底处理:其他所有类型直接打印 template <typename TChar, typename TTraits, typename TItem> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, TItem item); // 带分隔符打印可变参数(const char*/const wchar_t* 分隔符) template <typename TChar, typename TTraits, typename... Args> auto& PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> os, const TChar* separator, Args&&... args); // 带分隔符打印可变参数(std::string/std::wstring 分隔符) template <typename TChar, typename TTraits, typename... TArgs> auto& PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> os, std::basic_string<TChar, TTraits> separator, TArgs&&... args); // 带分隔符打印可变参数(std::string_view/std::wstring_view 分隔符) template <typename TChar, typename TTraits, typename... TArgs> auto& PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> os, std::basic_string_view<TChar, TTraits> separator, TArgs&&... args); //////////////////////////////////////////////////////////////////////////////// // 具体实现 // const char* template <typename TChar> std::basic_string<TChar> EscapeForPrinting(const TChar* text) { std::regex regex = std::regex(StringTraits<TChar>::Literal("\033", L"\033")); const TChar* replace = StringTraits<TChar>::Literal("\\033", L"\\033"); return std::regex_replace(text, regex, replace); } // std::string template <typename TChar, typename TTraits> std::basic_string<TChar, TTraits> EscapeForPrinting(const std::basic_string<TChar, TTraits>& text) { std::regex regex = std::regex(StringTraits<TChar>::Literal("\033", L"\033")); std::basic_string<TChar, TTraits> replace = StringTraits<TChar>::Literal("\\033", L"\\033"); return std::regex_replace(text, regex, replace); } // std::string_view template <typename TChar, typename TTraits> std::basic_string<TChar, TTraits> EscapeForPrinting(const std::basic_string_view<TChar, TTraits>& text) { std::basic_string<TChar, TTraits> text_as_string = std::basic_string<TChar, TTraits>(text); std::regex regex = std::regex(StringTraits<TChar>::Literal("\033", L"\033")); std::basic_string<TChar, TTraits> replace = StringTraits<TChar>::Literal("\\033", L"\\033"); return std::regex_replace(text_as_string, regex, replace); } // const char* template <typename TChar, typename TTraits> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const TChar* item) { os << StringTraits<TChar>::Literal("\"", L"\"") << EscapeForPrinting(item) << StringTraits<TChar>::Literal("\"", L"\""); return os; } // std::string template <typename TChar, typename TTraits> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const std::basic_string<TChar, TTraits>& item) { os << StringTraits<TChar>::Literal("\"", L"\"") << EscapeForPrinting(item) << StringTraits<TChar>::Literal("\"", L"\""); return os; } // std::string_view template <typename TChar, typename TTraits> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const std::basic_string_view<TChar, TTraits>& item) { os << StringTraits<TChar>::Literal("\"", L"\"") << EscapeForPrinting(item) << StringTraits<TChar>::Literal("\"", L"\""); return os; } // std::tuple template <typename TChar, typename TTraits, typename... TArgs> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, const std::tuple<TArgs...>& tuple) { std::apply( [&os](auto&&... args) { if (sizeof...(TArgs) == 0) { os << StringTraits<TChar>::Literal("[]", L"[]"); return; } size_t n = 0; os << StringTraits<TChar>::Literal("[ ", L"[ "); ((PrettyPrint(os, args) << (++n != sizeof...(TArgs) ? StringTraits<TChar>::Literal(", ", L", ") : StringTraits<TChar>::Literal("", L""))), ...); os << StringTraits<TChar>::Literal(" ]", L" ]"); }, tuple); return os; } // 容器 template <typename TChar, typename TTraits, typename TContainer, typename, typename> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, TContainer container) { os << StringTraits<TChar>::Literal("[ ", L"[ "); for (auto it = container.begin(); it != container.end(); it++) { if (it != container.begin()) { os << StringTraits<TChar>::Literal(", ", L", "); } PrettyPrint(os, *it); } os << StringTraits<TChar>::Literal(" ]", L" ]"); return os; } // 兜底处理 template <typename TChar, typename TTraits, typename TItem> auto& PrettyPrint(std::basic_ostream<TChar, TTraits>& os, TItem item) { os << item; return os; } // 带分隔符打印可变参数(const char* 分隔符) template <typename TChar, typename TTraits, typename... Args> auto& PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> os, const TChar* separator, Args&&... args) { ((os << args << EscapeForPrinting(separator)), ...); return os; } // 带分隔符打印可变参数(std::string 分隔符) template <typename TChar, typename TTraits, typename... TArgs> auto& PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> os, std::basic_string<TChar, TTraits> separator, TArgs&&... args) { (((PrettyPrint(os, args), os), EscapeForPrinting(separator)), ...); return os; } // 带分隔符打印可变参数(std::string_view 分隔符) template <typename TChar, typename TTraits, typename... TArgs> auto& PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> os, std::basic_string_view<TChar, TTraits> separator, TArgs&&... args) { (((PrettyPrint(os, args), os), EscapeForPrinting(separator)), ...); return os; }
内容的提问来源于stack exchange,提问作者majinnaibu
相关产品推荐
相关产品推荐

