Sunday, March 15, 2009

Using Template Specialization for Multi-format IO

I get the feeling that C++ templates have a reputation for being too difficult. That is how I felt about templates at first too. I think that everything seems difficult at first, that's because you don't understand it yet. You just need to dig in and eventually a lightbulb clicks on and it feels so natural all of the sudden because you understand it. A while I deiced to learn the STL and that is when I understood templates. Later on I even developed a pretty handy technique for doing mutli-format object serialization using templates.

The main idea is that you have a type for each format you want to support and then write a template specialization for the stream extraction (>>) and stream insertion (<<) operators for each type. Check it out:


#include <iostream>
#include <fstream>
#include <string>
using namespace std;

class oxmlstream : public ofstream {};
class ojsonstream : public ofstream {};

class contact
{
public:
contact(string name, string address)
{
_name = name;
_address = address;
}

string get_name() const {return _name;}
string get_address() const {return _address;}

private:
string _name;
string _address;
};

template<typename stream_type>
stream_type& operator<<(stream_type& stream, const contact& contact)
{
stream << contact.get_name() << ' ' << contact.get_address();
return stream;
}

template<> oxmlstream& operator<<(oxmlstream& xml, const contact& contact)
{
xml << "<contact>" << endl;
xml << " <name>" << contact.get_name() << "</name>" << endl;
xml << " <address>" << contact.get_address() << "</address>" << endl;
xml << "</contact>";

return xml;
}

template<> ojsonstream& operator<<(ojsonstream& json, const contact& contact)
{
json << "{" << endl;
json << " \"name\": \"" << contact.get_name() << "\"," << endl;
json << " \"address\": \"" << contact.get_address() << '\"' << endl;
json << "}";

return json;
}

int main(int argc, char* argv[])
{
contact dave("dave", "dave's address");
cout << dave << endl;

oxmlstream xout;
xout.open("dave.xml");
xout << dave << endl;
xout.close();

ojsonstream jout;
jout.open("dave.json");
jout << dave << endl;
jout.close();

return 0;
}


I skipped the corresponding stream extraction (input) overloads here but I think this should communicate the main idea. This keeps the input/output code outside of the class definition so you can focus on the information and behavior while working on the class. You can tackle the IO via the operator overloads after you've got the class working properly, and you are able to incrementally add support for the latest and greatest format (MGraph anyone).

No comments:

Post a Comment