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:
#include
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 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<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:
#include
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