0

I'm trying to overload the << operator so I can do, for example,

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

Another user of this site, Chris Redford, posted some helpful code for doing this with vectors at How to print out the contents of a vector? . I've attempted to adapt his code to work with other types of collection as follows:

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;
}

Obviously I'm a noob when it comes to writing templates, so any tips for reading materials would be welcome. Hopefully it is clear that I'd like this to work for anything that can do .begin() and .end(). When compiling this with

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

, I get a compiler error saying "ambiguous overload for 'operator<<'" and a bunch of gobbledy goop I don't understand. I think gcc might be trying to redefine what << means for std::string, but I'm not sure. Ideally, I'd like to tell the compiler not to try to redefine this operation for types for which it is already defined. I'm also using -std=C++14 so I'm open to clever use of auto. Any suggestions?

Edit: corrected bad use of T... in original question.

Community
  • 1
  • 1
  • Why do you use a template parameter pack i.e. typename... ? I can't see the need. – user2672165 Oct 13 '15 at 18:25
  • My advice: Do not do it. The template is not specific and may be harmful (preventing overload selections for dedicated containers). But this is only a guess. If you use it for debugging use a macro. –  Oct 13 '15 at 18:31
  • @user2672165 As I said, I'm new to templates, but I thought this kind of thing was necessary if I want this to work on generics of generics, like list>. –  Oct 13 '15 at 18:37
  • @DieterLücking So would your advice be to have a version of Chris Redford's code with all the different containers? I thought the point of templates was to avoid stuff like that. Also, since the template calls for .begin() and .end(), it seems like this wouldn't be harmful to anything that doesn't, correct? –  Oct 13 '15 at 18:40
  • Instead of overloading << operator you could make a template print-function to make it clearer. – user2672165 Oct 13 '15 at 18:47
  • "ambiguous overload for 'operator <<'" means your `operator<<` is also suitable for printing `std::string` and the compiler doesn't know which `operator<<` it should use - an operator from the standard library or yours one. Simply don't overload `operator<<`, write a usual function template with an appropriate name to avoid ambiguity. – Constructor Oct 14 '15 at 05:34
  • _"since the template calls for .begin() and .end(), it seems like this wouldn't be harmful to anything that doesn't, correct?"_ Wrong, those calls are only in the body of the function, so don't stop it being chosen by overload resolution and getting instantiated, which then fails if the type doesn't have `begin()` and `end()`. – Jonathan Wakely Nov 16 '15 at 18:03
  • @user2672165, how many template arguments does `std::vector` have? How about `std::set`? `std::map`? `std::unordered_map`? – Jonathan Wakely Nov 16 '15 at 18:04

3 Answers3

2

Just found following: Pretty-print C++ STL containers

Solution looks quite sophisticated. If you want to go for some more simple solution you could do the following:

Writing a template operator<< is very likely to conflict with any exisiting declaration of operator<<. What you could do is use a print function as already proposed and write some smaller wrappers, e.g.:

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);
}

// ...
Community
  • 1
  • 1
Joerg S
  • 4,730
  • 3
  • 24
  • 43
0

I've made something like that for a another project, but I had to make an "<<" for each type and not all types are implemented here.

#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;
}
Surt
  • 15,501
  • 3
  • 23
  • 39
0

There's a problem with your function:

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

That will try to print any type which is a specialization of a template! So it will be used for std::pair<int, double> and other non-containers, and will fail to compile because they don't have begin() and end() member functions.

The solution is to constrain the template so it only matches types that can be iterated over. This is based on my code in <redi/printers.h> and will print anything that can be passed to 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;
    }

You still have the problem that this function will match for types which already have their own overloaded operator<< and so will either do the wrong thing or will be ambiguous (that is a problem for std::string, for example).

In <redi/printers.h> I solve that by defining a different function to do the printing, called print_one, and the overload that prints ranges is disabled if the type can already be printed using operator<<.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521