C++重载 ostream <<以进行任意收集

C++ overloading ostream << for arbitrary collection

本文关键字:lt 任意收 重载 C++ ostream      更新时间:2023-10-16

我试图重载<<操作符,例如,

list<string> string_list = ...;
vector<double> double_vector = ...;
set<list<int>> int_list_set = ...;
cout << string_list << double_vector << int_list_set << endl;

这个网站的另一个用户,克里斯·雷德福,张贴了一些有用的代码来做这个与矢量在如何打印出矢量的内容?. 我试图修改他的代码,使其与其他类型的集合一起工作,如下所示:

template <template <typename...> class collection, typename T>
std::ostream& operator<<(std::ostream& out, const collection<T>& c)  {
  out << "[ ";
  out << *c.begin();
  for(auto it=next(c.begin(),1); it!=c.end(); ++it) {
    out << " , ";
    out << *it;
  }
  out << " ]";
  return out;
}

很明显,在写模板方面我是个新手,所以任何关于阅读材料的建议都是受欢迎的。希望这是清楚的,我希望这可以为任何可以做。begin()和。end()工作。当用

编译时
int main(int argc, char **argv) {
  list<string> words;
  words.push_back("hello");
  words.push_back("world");
  cout << words << endl;
}

,我得到一个编译器错误,说"对'operator<<'有歧义的重载"和一堆我不理解的废话。我认为gcc可能试图重新定义什么<<表示std::string,但我不确定。理想情况下,我想告诉编译器不要尝试为已经定义了该操作的类型重新定义该操作。我还使用了-std= c++ 14,所以我可以聪明地使用auto。有什么建议吗?

编辑:纠正错误使用T…

刚刚发现以下内容:c++ STL容器

解决方案看起来相当复杂。如果你想要一些更简单的解决方案,你可以这样做:

编写模板operator<<很可能与任何现有的operator<<声明相冲突。您可以做的是使用print函数,如已经提出的,并编写一些较小的包装器,例如:

template <class collection>
std::ostream& printCollection (std::ostream& out, const collection& c)  {
  out << "[ ";
  out << *c.begin();
  for(auto it = next(c.begin(), 1); it != c.end(); ++it) {
    out << " , ";
    out << *it;
  }
  out << " ]";
  return out;
}
template <typename T>
std::ostream& operator<< (std::ostream& os, std::list<T>& collection) {
  return printCollection(os, collection);
}
// ...

我在另一个项目中也做过类似的东西,但是我必须为每个类型都做一个"<<",这里并不是所有的类型都实现了。

#include <iostream>
#include <vector>
#include <list>
#include <utility>
#include <algorithm>
#include <string>
#include <array>
#include <set>
#include <map>
#include <assert.h>
using namespace std;
int indentCnt = 0;
static string indents = "  ";
class AddOne {
    int& counter;
public:
    AddOne(int& pre = indentCnt) : counter(pre) {
        counter++;
        if (indents.length()<2*counter)
            indents += indents;
     }
    ~AddOne() {
        counter--;
    }
};
string indent() {
    assert(indents.length() >= 2*indentCnt);
    return indents.substr(0, 2*indentCnt);
}
enum delimiters { deBefore, deBetween, deAfter, deNum };
using delims = array<string, deNum>;
template<typename cType>
std::ostream& forallout(std::ostream& out, const cType& v, const delims& delim) {
    auto it = v.begin();
    out << delim[deBefore];
    if (it != v.end()) {
        for (; it != prev(v.end()); it++) // to avoid the seperator after last.
            out << *it << delim[deBetween];
        out << *it;
    } else
        out << "~Empty~";
    out << delim[deAfter];
    return out;
}
template <typename kType, typename dType>
std::ostream& operator<<(std::ostream& out, const std::map<kType, dType>& c)  {
    delims de { indent()+"[n  "+indent(), ",n  "+indent(), "n"+indent()+"]" };
    AddOne aMap(indentCnt);
    return forallout(out, c, de);
}
template <typename dType>
std::ostream& operator<<(std::ostream& out, const std::vector<dType>& c)  {
    delims de { indent()+"[n", ",n", "n"+indent()+"]" };
    AddOne aVec(indentCnt);
    return forallout(out, c, de);
}
template <typename dType>
std::ostream& operator<<(std::ostream& out, const std::list<dType>& c)  {
    delims de { indent()+"(", "<->", ")" };
    return forallout(out, c, de);
}
template <typename dType>
std::ostream& operator<<(std::ostream& out, const std::set<dType>& c) {
    delims de { indent()+"{", ", ", "}" };
    return forallout(out, c, de);
}
template <typename dType, typename kType>
std::ostream& operator<<(std::ostream& out, const std::pair<kType, dType>& c)  {
    delims de { "[", ":", "]" };
    out << de[deBefore] << c.first << de[deBetween] << c.second << de[deAfter];
    return out;
}
template <typename kType>
std::ostream& operator<<(std::ostream& out, const std::pair<kType, string>& c)  {
    delims de { "[", ":", "]" };
    out << de[deBefore] << c.first << de[deBetween] << """ << c.second << """ << de[deAfter];
    return out;
}

你的函数有问题:

template <template <typename...> class collection, typename T>
std::ostream& operator<<(std::ostream& out, const collection<T>& c)  {

将尝试打印任何类型,这是一个模板的专门化!因此,它将用于std::pair<int, double>和其他非容器,并且将无法编译,因为它们没有begin()end()成员函数。

解决方案是约束模板,使其只匹配可以迭代的类型。这是基于我在<redi/printers.h>中的代码,并将打印任何可以传递给std::begin的内容:

  template<typename Range,
           typename = decltype(*std::begin(std::declval<Range>()))>
    std::ostream&
    operator<<(std::ostream& os, const Range& range)
    {
      os << '[';
      const char* sep = "";
      for (auto& e : range)
      {
        os << sep << e;
        sep = ", ";
      }
      os << ']';
      return os;
    }

你仍然有一个问题,这个函数将匹配已经有自己的重载operator<<的类型,因此要么会做错误的事情,要么会模棱两可(这是std::string的问题,例如)。

<redi/printers.h>中,我通过定义一个不同的函数来执行打印来解决这个问题,称为print_one,如果已经可以使用operator<<打印类型,则禁用打印范围的过载。