Joe Yates' Blog

Programming and DevOps

Overriding the Insertion Operator for C++ Template Classes

Dump an Active Record object to Standard Out - dump_active_record_object.cpp
1
2
Person person( 123 );
std::cout << person << endl;

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

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

Insertion Operator - insertion_operator.cpp
1
ostream & operator<<( ostream &out_stream, const C& c );

This is a global function that can called like this:

Calling the Insertion Operator - calling_insertion_operator.cpp
1
std::cout << c;

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

Dumping Objects - dump_object.cpp
1
2
3
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:

MyList - my_list.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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.

Using MyList - using_my_list.cpp
1
2
3
4
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:

Insertion Operator for MyList - my_list_insertion_operator.cpp
1
2
3
4
5
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<T> member and output its values.

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

Incorrect MyList Iteration 1 - my_list_iteration1.cpp
1
2
3
...
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:

MyList Iteration - my_list_iteration.cpp
1
2
3
...
for( typename list<T>::const_iterator it = a_list.data_.begin(); it != a_list.data_.end(); ++it ) {
...

Here’s the final implementation:

MyList Insertion Operator - my_list_insertion_operator.cpp
1
2
3
4
5
6
7
8
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:

Making the Insertion Operator a Friend - insertion_operator_friend.cpp
1
2
3
4
5
6
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:

MyList - my_list.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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:

Using MyList - using_my_list.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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