几乎什么都能转。

把tuple转字符串最难写。

#include <bits/stdc++.h>

namespace ext {

template <typename StrType>
std::string ToString(const StrType& str);

template <typename StrType>
std::string ToString(const std::vector<StrType>& vec);

template <typename... Args>
std::string ToString(const std::tuple<Args...>& tpl);

template <typename K, typename D, typename A>
std::string ToString(const std::set<K, D, A>& value);

template <typename K, typename D, typename A>
std::string ToString(const std::multiset<K, D, A>& value);

template <typename K, typename H, typename D, typename A>
std::string ToString(const std::unordered_set<K, H, D, A>& value);

template <typename K, typename V, typename D, typename A>
std::string ToString(const std::map<K, V, D, A>& value);

template <typename K, typename V, typename D, typename A>
std::string ToString(const std::multimap<K, V, D, A>& value);

template <typename K, typename V, typename H, typename D, typename A>
std::string ToString(const std::unordered_map<K, V, H, D, A>& value);

template <typename... Args>
std::string ToString(const std::pair<Args...>& tpl);

std::string ToString(const std::string& value);

namespace detail {

template <typename Tuple, int pos>
struct ToStringImpl {
  static std::string Impl(const Tuple& tpl) {
    return ToStringImpl<Tuple, pos - 1>::Impl(tpl).append(ext::ToString(std::get<pos>(tpl))).append(pos + 1 == static_cast<int>(std::tuple_size_v<Tuple>) ? "" : ", ");
  }
};

template <typename Tuple>
struct ToStringImpl<Tuple, -1> {
  static std::string Impl(const Tuple& tpl) {
    return std::string();
  }
};

template <typename Tuple>
struct ToStringImpl<Tuple, 0> {
  static std::string Impl(const Tuple& tpl) {
    return ext::ToString(std::get<0>(tpl)).append(1 == static_cast<int>(std::tuple_size_v<Tuple>) ? "" : ", ");
  }
};

template <typename SetType>
std::string ToStringSetImpl(const SetType& value) {
  std::string res = "{";
  for (typename SetType::const_iterator iter = value.begin(); iter != value.end(); iter++) {
    if (iter != value.begin()) {
      res.append(", ");
    }
    res.append(ToString(*iter));
  }
  return res.append("}");
}

template <typename MapType>
std::string ToStringMapImpl(const MapType& value) {
  std::string res = "{";
  for (typename MapType::const_iterator iter = value.begin(); iter != value.end(); iter++) {
    if (iter != value.begin()) {
      res.append(", ");
    }
    res.append(ToString(iter->first));
    res.append(": ");
    res.append(ToString(iter->second));
  }
  return res.append("}");
}

}  // namespace detail

template <typename StrType>
std::string ToString(const StrType& str) {
  std::ostringstream oss;
  oss << str;
  return oss.str();
}

template <typename StrType>
std::string ToString(const std::vector<StrType>& vec) {
  std::string res = "[";
  const int n = vec.size();
  for (int i = 0; i < n; i++) {
    res.append(ToString(vec[i])).append((i + 1 == n ? "" : ", "));
  }
  res.append("]");
  return res;
}

template <typename... Args>
std::string ToString(const std::tuple<Args...>& tpl) {
  std::string res = "(";
  res.append(detail::ToStringImpl<std::tuple<Args...>, static_cast<int>(std::tuple_size_v<std::tuple<Args...>>) - 1>::Impl(tpl));
  res.append(")");
  return res;
}

template <typename K, typename D, typename A>
std::string ToString(const std::set<K, D, A>& value) {
  return detail::ToStringSetImpl(value);
}

template <typename K, typename D, typename A>
std::string ToString(const std::multiset<K, D, A>& value) {
  return detail::ToStringSetImpl(value);
}

template <typename K, typename H, typename D, typename A>
std::string ToString(const std::unordered_set<K, H, D, A>& value) {
  return detail::ToStringSetImpl(value);
}

template <typename K, typename V, typename D, typename A>
std::string ToString(const std::map<K, V, D, A>& value) {
  return detail::ToStringMapImpl(value);
}

template <typename K, typename V, typename D, typename A>
std::string ToString(const std::multimap<K, V, D, A>& value) {
  return detail::ToStringMapImpl(value);
}

template <typename K, typename V, typename H, typename D, typename A>
std::string ToString(const std::unordered_map<K, V, H, D, A>& value) {
  return detail::ToStringMapImpl(value);
}

template <typename... Args>
std::string ToString(const std::pair<Args...>& tpl) {
  std::string res = "(";
  res.append(detail::ToStringImpl<std::pair<Args...>, 1>::Impl(tpl));
  res.append(")");
  return res;
}

std::string ToString(const std::string& value) {
  return value;
}

void Debug() {
  std::cerr << '\n';
}

template <typename Head, typename... Tail>
void Debug(const Head& head, const Tail&... tail) {
  std::cerr << " " << ToString(head);
  Debug(tail...);
}

}  // namespace ext

#ifdef local
#define DEBUG(...) do { std::cerr << "[" << #__VA_ARGS__ << "]:"; ext::Debug(__VA_ARGS__); } while (0)
#else
#define DEBUG(...) do { } while (0)
#endif

int main() {
  std::cout << ext::ToString(std::vector<std::vector<int>>({{1, 2}, {2}})) << "\n";
  std::cout << ext::ToString(std::make_tuple<int, int, std::string>(1, 2, "-")) << "\n";
  std::cout << ext::ToString(std::make_tuple(1, std::make_tuple(2))) << '\n';
  std::cout << ext::ToString(std::make_tuple()) << '\n';
  std::cout << ext::ToString(std::map<int, std::tuple<int, int>> ({{1, {1, 1}}, {2, {-1, 1}}})) << '\n';
  std::cout << ext::ToString(std::make_pair(1, std::make_tuple(1, 3))) << '\n';
  std::cout << ext::ToString(std::unordered_set<int> ({1, 2, 3, 4})) << '\n';
  DEBUG(1, std::make_pair(1, 2), std::make_pair(1, std::make_tuple(1, "3")));
}