Monday, October 18, 2010

C++ Overloading operator<< for std::ostream

The operator<< is used to stringify an object and "shift" the result to an output stream, such as std::cout for standard output. It is used like this:
#include <iostream>
int main() {
  std::cout << "hello world" << std::endl;
  return 0;
}
This of course only works for values of types for which there is an overloading of the operator<<. If you want to implement serialization for a new class, you will have to implement an operator<<() in the global scope, and cannot do so in the class scope. That is because the left hand side of << is an std::ostream& object, not your object.

For example, this is right:
class Foo {...};

std::ostream& operator<<(std::ostream& os, const Foo& foo) {
  ...
}
But this will not result in the intended notation:
class Foo {
  ...

  std::ostream& operator<<(std::ostream& os) {
    // stringify this object to output stream os.
    ...
  }
};
This means that a statement like foo << std::cout will stream foo to std::cout, but the notation is now wrong. You can overload operator>> instead, but this notation has other issues we will not discuss here.

Sometimes we do not want to overload operator<< globally, at least we don't want to pollute the global namespace with a different operator<< for each serializable class. We can do this by introducing an indirection layer with subtyping polymorphism.
class serializable {
 public:
  virtual void serialize(std::ostream& os) const = 0;
};

std::ostream& operator<< (std::ostream& os, const serializable& s) {
  s.serialize(os);
  return os;
}

class Foo : public serializable {
 public:
  virtual void serialize(std::ostream& os) const {
    os << "Foo" << std::endl;
  }
};
Then we can simply say:
int main() {
  Foo f;
  std::cout << f;
  return 0;
}
We implement exactly one global operator<< trampoline, which calls the serialize() method of a serializable abstract base class. Any subclass of a serializable that implements the serialize() method can now also be used with operator<< automatically.

If you don't want to use virtual table, a similar technique using Curiously Recurring Template Pattern also works.
template<class Derived>
class serializable {
 public:
  void serialize(std::ostream& os) const {
    static_cast<const Derived *>(this)->serialize(os);
  }
};

template<class Derived>
std::ostream&
operator<< (std::ostream& os, const serializable<Derived>& s) {
  s.serialize(os);
  return os;
}

class Foo : public serializable<Foo> {
 public:
  void serialize(std::ostream& os) const {
    os << "Foo" << std::endl;
  }
};

We can similarly define unserializable class with operator>> analogously. This is left as an exercise for the reader.

1 comment:

Ant said...

Thank you. Very useful. Ant