May 16
Stream Formats
A technique to support multiple formats in C++ Stream I/O.
I was writing a class for a kind of status message which could be output in either XML or a plain text format. I wanted the class to be usable with C++ stream operators << and >>, but because there were two formats it wasn’t easy to see which one the stream operators should work with.
I remember when I first encountered C++ Streams I was impressed by the elegance of the design. The Borland C++ Programmers Reference manual had a great explanation of the concept. [Unfortunately I'm not sure what happened to my copy, so I'll just give my own crappy description here.] A stream is built up from a source or a sink, manipulators and the ‘objects’ which are being streamed, all connected with the put-to (<<) or get-from (>>) operators. I still consider it superior to the clumsy flags and varargs approach of scanf and printf. But despite this it is not often that I define put-to and get-from operators for my own classes. Instead I’ve often ended up defining custom input and output functions. Hopefully the technique below will make it easy for me to use stream operators instead.
template <typename T, typename U>
struct StreamFormat
{
StreamFormat(T& t) : m_t(t) {}
T& m_t;
};
StreamFormat is a template which just holds a reference to some object of type T, but it is also disambiguated by the format type U.
What this means is for a type we can define multiple format type wrappers.
typedef StreamFormat<Thing, struct Xml> AsXml; typedef StreamFormat<Thing, struct String> AsString;
The struct Xml and struct String are incomplete types which are just used to differentiate the format types. (They’re never used again once they’ve served that purpose.)
Now we have an interface which which can support different formats of stream I/O.
thingss >> Thing::AsString(thing);
cout << Thing::AsXml(thing) << endl;
But how is this I/O implemented? The StringFormat template is accompanied by generic stream operators
template <typename T, typename U>
istream& operator >>(istream& lhs, StreamFormat<T,U> rhs)
{
return rhs.m_t.getFrom(lhs, rhs);
}
template <typename T, typename U>
ostream& operator <<(ostream& lhs, StreamFormat<T,U> rhs)
{
return rhs.m_t.putTo(lhs, rhs);
}
And as shown these delegate back to the wrapped type’s getFrom and putTo member functions which will be overloaded by a StreamFormat argument. These do the real work, and as they are member functions have direct access to the member variables.
istream& Thing::getFrom(istream& in, AsXml)
{
// world's dirtiest XML parsing
in.ignore(numeric_limits<streamsize>::max(), '>');
in >> m_a;
in.ignore(numeric_limits<streamsize>::max(), '>');
return in;
}
ostream& Thing::putTo(ostream& out, AsXml) const
{
out << "<thing>" << m_a << "</thing>";
return out;
}
istream& Thing::getFrom(istream& in, AsString)
{
in >> m_a;
return in;
}
You might be asking “why not just call the getFrom and putTo methods directly?”. To which I say “didn’t you read what I said about the elegance of the stream operators?!” and “having them as stream operators makes them easy to mix with other standard C++ objects” and “they can be used in standard algorithms with the istream_iterator and ostream_iterator wrappers”.
I did think about having member functions which would construct the StreamFormat objects.
AsXml asXml() { return AsXml(*this); }
They make things a little bit neater, but I don’t think they’re really worth it.
thingss >> thing.asXml();
Finally here’s all the code of a little test program I wrote. Does anyone else use this technique? Is there a better way? Or do you think it is all unneeded syntactic sugar? “mmmmm sweet sweet syntactic sugar.”
#include <iostream>
#include <sstream>
#include <limits>
using namespace std;
template <typename T, typename U>
struct StreamFormat
{
StreamFormat(T& t) : m_t(t) {}
T& m_t;
};
template <typename T, typename U>
istream& operator >>(istream& lhs, StreamFormat<T,U> rhs)
{
return rhs.m_t.getFrom(lhs, rhs);
}
template <typename T, typename U>
ostream& operator <<(ostream& lhs, StreamFormat<T,U> rhs)
{
return rhs.m_t.putTo(lhs, rhs);
}
class Thing
{
public:
Thing() : m_a(0) {}
Thing(int a) : m_a(a) {}
int geta() const { return m_a; }
typedef StreamFormat<Thing, struct Xml> AsXml;
istream& getFrom(istream& in, AsXml);
ostream& putTo(ostream& out, AsXml) const;
typedef StreamFormat<Thing, struct String> AsString;
istream& getFrom(istream& in, AsString);
private:
int m_a;
};
istream& Thing::getFrom(istream& in, AsXml)
{
// world's dirtiest XML parsing
in.ignore(numeric_limits<streamsize>::max(), '>');
in >> m_a;
in.ignore(numeric_limits<streamsize>::max(), '>');
return in;
}
ostream& Thing::putTo(ostream& out, AsXml) const
{
out << "<thing>" << m_a << "</thing>";
return out;
}
istream& Thing::getFrom(istream& in, AsString)
{
in >> m_a;
return in;
}
int main(int argc, char *argv[])
{
Thing thing;
stringstream thingss;
thingss.str("<thing>99</thing>");
thingss >> Thing::AsXml(thing);
cout << thing.geta() << endl;
thingss.str("86");
thingss >> Thing::AsString(thing);
cout << Thing::AsXml(thing) << endl;
return 0;
}

Started a new job today. And as a bonus I’ve been given a Solaris/Linux project to work on. Now just waiting to get a Solaris/Linux box to do it on.







