Overriding the Insertion Operator for C++ Template Classes

2011/03/06

How to implement the << operator

While working on my C++ ActiveRecord implementation, I had a few problems implementing the insertion operator for the main ActiveRecord::Base class.

I wanted to dump an Active Record object to Standard Out:

Person person(123);
std::cout << person << endl;

The class ActiveRecord::Base class is a template class, and the problem was how to correctly declare the operator.

At the time I was unable to find examples on the Internet, so I thought I'd provide my own.

The Insertion Operator

By "insertion operator" I mean

ostream & operator<<(ostream &out_stream, const C& c);

This is a global function that can be called like this:

std::cout << c;

This operator allows you to output a string representation of your classes, which is very handy for debugging:

C c;
c.do_stuff();
std::cout << c << endl;

The Example

A Class

Here's an example class which (rather reduntantly) wraps an STL std::list:

#include <list>

using namespace std;

template < class T >
class MyList {
 public:
  MyList() {};

  void add( T item ) {
    data_.push_back( item );
  }

  int length() const {
    return data_.size();
  }

 private:
  list< T > data_;
};

Serialization

While debugging and testing, it would be handy to be able to output a string representation of an instance of this class.

MyList<int> ints;
ints.add(42);
ints.add(13);
std::cout << ints << endl;
Which should work like this:
$ ./my_list
2 items:
  42
  13

Implementation

Here's an implementation that just outputs the number of items:

template< class T >
ostream & operator<<( ostream &out_stream, const MyList< T >& a_list ) {
  out_stream << a_list.length() << " items:" << endl;
  return out_stream;
}

Next, we want the operator to iterate over the list member and output its values.

The main sticking point for me here was the declaration of the iterator. The following is not sufficient:

...
for( list<T>::const_iterator it = a_list.data_.begin(); it != a_list.data_.end(); ++it ) {
...

and produces this error message (with gcc):

to_ostream.cpp: In function 'std::ostream& operator<<(std::ostream&, const MyList<T>&)':
to_ostream.cpp:7: error: expected `;' before 'it'
to_ostream.cpp:7: error: 'it' was not declared in this scope

What's missing is the typename keyword to tell the compiler that we're instantiating an iterator:

...
for(typename list<T>::const_iterator it = a_list.data_.begin(); it != a_list.data_.end(); ++it) {
...

Here's the final implementation:

template< class T >
ostream & operator<<( ostream &out_stream, const MyList< T >& a_list ) {
  out_stream << a_list.length() << " items:" << endl;
  for( typename list<T>::const_iterator it = a_list.data_.begin(); it != a_list.data_.end(); ++it ) {
    out_stream << "\t" << *it << endl;
  }
  return out_stream;
}

The function also has to access the private data_ member, which entails declaring it as a friend:

template < class T >
class MyList {
 template< class T1 >
 friend ostream & operator<<( ostream &out_stream, const MyList< T1 >& a_list );
 ...
};

Here's the whole class with the operator:

#include <iostream>
#include <list>

using namespace std;

template < class T >
class MyList {
 template< class T1 >
 friend ostream & operator<<( ostream &out_stream, const MyList< T1 >& a_list );
 public:
  void add( T item ) {
    data_.push_back( item );
  }
  
  int length() const {
    return data_.size();
  }
 private:
  list< T > data_;
};

template< class T >
ostream & operator<<( ostream &out_stream, const MyList< T >& a_list ) {
  out_stream << a_list.length() << " items:" << endl;
  for( typename list<T>::const_iterator it = a_list.data_.begin(); it != a_list.data_.end(); ++it ) {
    out_stream << "\t" << *it << endl;
  }
  return out_stream;
}

And here's an example of it's use:

#include <string>

int main( int argc, char **argv ) {
  MyList< int > my_ints;
  my_ints.add( 1 );
  my_ints.add( 2 );

  cout << "myints" << endl;
  cout << my_ints << endl;

  MyList< string > my_strings;
  my_strings.add( "hello" );
  my_strings.add( "world" );

  cout << "mystrings" << endl;
  cout << my_strings << endl;

  return 0;
}

The output:

$ ./to_ostream 
myints
2 items:
        1
        2

mystrings
2 items:
        hello
        world