You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 ]"

实现过程中遇到的问题

  1. 初始版本仅支持std::vector,未实现字符串自动加引号逻辑
  2. 尝试函数模板偏特化时触发Clang错误:Function template partial specialization is not allowed
  3. 改用通用容器模板时与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++) {
                                          ~~~~~~~~~ ^
  1. 存在指针误匹配字符串打印重载的问题

解决方案与最终实现

通过移除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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 06:07:03