2011/3/6
Dump an Active Record object to Standard Out:
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
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:
#includeusing 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.
MyListints; 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<T> 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::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&)': 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::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::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#include 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
::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:
#includeint 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