递归AsString()以在C ++中打印STL容器

时间:2011-01-27 17:58:50

标签: c++ templates

我正在尝试编写一个AsString()函数,根据我的喜好将STL容器转换为字符串。这是我到目前为止提出的代码:

template<class T>
inline string AsString(const T& v);

template<class First, class Second>
inline string AsString(const pair<First, Second>& p);

template<class Iter>
inline string PrintSequence(const char* delimiters, Iter begin, Iter end) {
  string result;
  result += delimiters[0];
  int size = 0;
  for (size = 0; begin != end; ++size, ++begin) {
    if (size > 0) {
      result += ", ";
    }
    result += AsString(*begin);
  }
  result += delimiters[1];
  result += StringPrintf("<%d>", size);
  return result;
}

#define OUTPUT_TWO_ARG_CONTAINER(Sequence) \
template<class T1, class T2> \
inline string AsString(const Sequence<T1, T2>& seq) { \
  return PrintSequence("[]", seq.begin(), seq.end()); \
}

OUTPUT_TWO_ARG_CONTAINER(vector)
OUTPUT_TWO_ARG_CONTAINER(deque)
OUTPUT_TWO_ARG_CONTAINER(list)

template<class First, class Second>
inline string AsString(const pair<First, Second>& p) {
  return "(" + AsString(p.first) + ", " + AsString(p.second) + ")";
}

template<class T>
inline string AsString(const T& v) {
  ostringstream s;
  s << v;
  return s.str();
}

正如您所看到的,基本思想是AsString()以递归方式在STL容器上调用自身,然后降低到通常的operator<<()(我不想覆盖{{1}的原因是因为我不想干涉那些完全相同的其他库。

现在,operator<<()编译并处理浅容器,但不适用于嵌套容器:

AsString()

由于某种原因,编译器在尝试打印“m”元素时想要使用vector<int> v; v.push_back(1); v.push_back(2); AsString(v) == "[1, 2]<2>"; // true vector<vector<int> > m; m.push_back(v); m.push_back(v); AsString(m) == "[[1, 2]<2>, [1, 2]<2>]<2>"; // Compilation Error!!! ,尽管事实上我已经为矢量提供了模板特化。

如何让operator<<()工作?

UPDATE :好的,结果定义的顺序很重要(至少对于这个编译器 - gcc 4.4.3)。当我首先放置宏定义时,编译器将正确地拾取它们并显示向量向量。令人费解的。

2 个答案:

答案 0 :(得分:3)

模板世界非常精彩......而且是一个真正陷入困境的陷阱......

专业化,是采用现有的模板函数并指定其所有参数。

重载正在重用与另一个函数(无论是否是模板)相同的名称来表示不同的参数集。

template <typename T>
void foo(T const& t);

template <>
void foo<int>(int i); // this is a "complete" specialization

template <typename T, typename U>
void foo<std::pair<T,U>>(std::pair<T,U> const& pair);
  // this is a "partial" specialization
  // and by the way... it does NOT COMPILE

template <typename T, typename U>
void foo(std::pair<T,U> const& pair); // this is an overload

注意语法差异,在重载中标识符后面没有<xxxx>foo这里)。

在C ++中,不可能部分地专门化一个函数;那就是在论证中保留一些通用性。您可以重载或完全专门化:此时必须阅读GotW #49: Template Specialization and Overloading

因此,选择是:

template <typename T>
std::string AsString(const T& v); // (1)

template <typename T, typename Allocator>
std::string AsString(std::vector<T, Allocator> const& v); // (2)

真正的问题是:*begin的类型是什么?

好吧,m不是const限定的:

  • Iter逻辑上是std::vector< std::vector<int> >::iterator
  • 因此*begin的类型为std::vector<int>&

因此考虑两个重载:

  • (1):T = std::vector<int>,需要转换为const-ref
  • (2):T = int, U = std::allocator<int>,需要转换为const-ref

应该选择第二个,因为它更接近真实类型,据我所知。我用VC ++ 2010对它进行了测试,实际上它已经被选中了。

你能否声明一个非const限定版本的向量重载,看看它是否会使你的编译器安抚? (顺便说一句,我想知道它的名字)。)。

答案 1 :(得分:1)

你没有提供专业化,你有重载 AsString。碰巧的是,你的后期过载并不比T const&amp;版本

相反,overload op<< in a special namespace代表各种stdlib容器。命名空间很重要,因此您不会影响其他代码,但您将在AsString中明确使用它:

namespace make_sure_to_put_these_overloads_in_a_namespace {

// Your PrintSequence adapted to a stream instead of a string:
template<class Iter>
void PrintSequence(std::ostream &s, const char* delim,
                   Iter begin, Iter end)
{
  s << delim[0];
  int size = 0;
  if (begin != end) {
    s << *begin;
    ++size;
    while (++begin != end) {
      s << ", " << *begin;
      ++size;
    }
  }
  s << delim[1] << '<' << size << '>';
}

#define OUTPUT_TWO_ARG_CONTAINER(Sequence) \
template<class T1, class T2> \
std::ostream& operator<<(std::ostream &s, Sequence<T1, T2> const &seq) { \
  PrintSequence(s, "[]", seq.begin(), seq.end()); \
  return s; \
}

OUTPUT_TWO_ARG_CONTAINER(std::vector)
OUTPUT_TWO_ARG_CONTAINER(std::deque)
OUTPUT_TWO_ARG_CONTAINER(std::list)
// other types
#undef OUTPUT_TWO_ARG_CONTAINER

template<class First, class Second>
std::ostream& operator<<(std::ostream &s, std::pair<First, Second> const &p) { \
  s << "(" << p.first << ", " << p.second << ")";
  return s;
}

}

template<class T>
std::string AsString(T const &v) {
  using namespace make_sure_to_put_these_overloads_in_a_namespace;
  std::ostringstream ss;
  ss << v;
  return ss.str();
}