Search code examples
c++classtemplatesostream

How to implement multiple insertion operators (<<) for a C++ class?


I output my class objects using cout or ofstream. I understood that the insertion operator << is the standard way to convert an object to a string or print it. I would like to use different formats in different parts of the project.

I would like to have many different string formats for the class. However, there could be only one insertion operator for the class.

What are the common ways to solve this in C++?

One solution I have used is to have multiple functions returning strings. My understanding is that overloading the insertion operator is the most common way to output objects and I would like to have that kind of solution.

Using different formats sounds like the strategy pattern. I guess a template is also a powerful tool for a lot of problems. I am not familiar with either kind of solution.

Here is a code example to demonstrate the problem:

#include <iostream>
#include <sstream>

class Point {
  public:
    Point(int x, int y): x(x), y(y) {}

    std::string to_string() {
      std::ostringstream stream;
      stream << "(" << x << ", " << y << ")";
      return stream.str();
    }   

    friend std::ostream& operator<<(std::ostream& out, const Point& p) {
      return out << p.x << " " << p.y;
    }   

  private:
    int x, y;
};

int main() {
  Point p { 4, 2 };
  std::cout << p << std::endl;
  std::cout << p.to_string() << std::endl;
}

I would like to get rid of the to_string function and use insertion operators to get all formats. What is an easy way to define which format insertion operator is using. It should be possible to change the format of the object.

There could be more than two different formats for the class.


Solution

  • You can define your own I/O stream manipulators for this task. You can either:

    • wrap the Point object with another struct/class type that you overload operator<< for, eg:
    #include <iostream>
    
    class Point {
      public:
        Point(int x, int y): x(x), y(y) {}
      private:
        int x, y;
        friend struct s_Point_printer;
    };
    
    struct s_Point_printer {
        const Point &m_pt;
        const bool m_parenthesis;
    
        s_Point_printer(const Point &pt, bool parenthesis) : m_pt(pt), m_parenthesis(parenthesis) {}
    
        void printTo(std::ostream &out) const {
            if (m_parenthesis)
                out << "(" << m_pt.x << ", " << m_pt.y << ")";
            else
                out << m_pt.x << " " << m_pt.y;
        }
    
        friend std::ostream& operator<<(std::ostream &out, const s_Point_printer &printer) {
            printer.printTo(out);
            return out;
        }
    };
    
    s_Point_printer with_parens( const Point& pt ) {
        return s_Point_printer{pt, true};
    }
    
    s_Point_printer without_parens( const Point& pt ) {
        return s_Point_printer{pt, false};
    }
    
    int main() {
      Point p { 4, 2 };
      std::cout << without_parens(p) << std::endl; // prints "4 2"
      std::cout << with_parens(p) << std::endl;    // prints "(4, 2)"
    }
    

    Online Demo

    • Alternatively, you can store your desired formatting option directly inside the std::ostream itself, and then have your overloaded operator<< for Point check that option, eg:
    class Point {
      public:
        static int point_xalloc;
    
        Point(int x, int y): x(x), y(y) {}
    
        friend std::ostream& operator<<(std::ostream& os, const Point& pt) {
            if (os.iword(Point::point_xalloc) == 1)
                return os << "(" << pt.x << ", " << pt.y << ")";
            else
                return os << pt.x << " " << pt.y;
        }
    
      private:
        int x, y;
    };
     
    int Point::point_xalloc = std::ios_base::xalloc();
    
    std::ios_base& with_parens(std::ios_base& os) {
        os.iword(Point::point_xalloc) = 1;
        return os;
    }
     
    std::ios_base& without_parens(std::ios_base& os) {
        os.iword(Point::point_xalloc) = 0;
        return os;
    }
    
    int main() {
      Point p { 4, 2 };
      std::cout << without_parens << p << std::endl; // prints "4 2"
      std::cout << with_parens << p << std::endl;    // prints "(4, 2)"
    }
    

    Online Demo