Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VC9.0

Using C++ templates for functions and operators

2.38/5 (5 votes)
3 Jun 2008CPOL3 min read 1   188  
Example on how templates can help you to generalize your code

Introduction

The other day I was writing some archive class for one of my projects and I need to serialize different types of data through the archive. So I started of writing a set of << and >> operators to handle all those different data types when suddenly a light bulb went off above my head: "Why not use template, I thought to myself…", and indeed why not? What are those C++ templates you might ask? Well I am pretty sure that you are familiar with the STL classes like <string>, <vector>, <list> etc… All those classes use templates. You can define a vector of integers like that:
vector<int> myintvector;
and that is possible because the vector class was declared using template directive.

Background

So how could templates help me to cover all those different data types I needed? Well basically what template does is allowing you to parameterize the definition itself of the class or function. Which means that the class or function need not to be defined with a specific data type in mind but made more generic and work with many if not all data types.

Using the code

Getting back to my archive class if I hadn’t use templates this is how the class definition would look like:

C++
class mystream
{
public:
    mystream(LPCSTR fileName, STREAMMODE m);
    ~mystream(void);
    
// Import data    
    mystream& operator>>(char& val);
    mystream& operator>>(short& val);
    mystream& operator>>(int& val);
    mystream& operator>>(float& val);
    mystream& operator>>(bool& val);
    mystream& operator>>(double& val);
    mystream& operator>>(long& val);
    mystream& operator>>(USHORT& val);
    mystream& operator>>(UINT& val);
    mystream& operator>>(ULONG& val);    

// Export data
    mystream& operator<<(const char& val);
    mystream& operator<<(const short& val);
    mystream& operator<<(const int& val);
    mystream& operator<<(const float& val);
    mystream& operator<<(const bool& val);
    mystream& operator<<(const double& val);
    mystream& operator<<(const long& val);
    mystream& operator<<(const USHORT& val);
    mystream& operator<<(const UINT& val);
    mystream& operator<<(const ULONG& val);
    
virtual void Write(const void* data, unsigned long size);
    virtual void Read(void* data, unsigned long size);

private:
    FILE* m_filePtr;    
};

As you can see there is an operator overload for each data type, and I haven't even touched the serialization of different arrays. So let's now try to convert all this to templated solution. First declare our class:
C++
class mystream
{
public:
    mystream(LPCSTR fileName, STREAMMODE m);
    ~mystream(void);
    
virtual void Write(const void* data, unsigned long size);
    virtual void Read(void* data, unsigned long size);

private:
    FILE* m_filePtr;    
};

As you can see no import/export operators are present. Let's declare an export and import operator:
C++
template<class T>
inline mystream & operator<<(mystream& s, T val)
{
    s.Write(&val,sizeof(T));
        return s;
}

template<class T>
inline mystream & operator>>(mystream& s, T& val)
{
s.Read(&val,sizeof(T));
        return s;
} 

As you can see I precede the function declaration with a "template" keyword and I use the definition of T as the data type of the function parameter. What will happen is that T will receive a different meaning depending on what data type I pass into the function. So if I write this code:
C++
mystream s("C:\\test.dat", write);
DWORD somevalue = 123;
s << somevalue;

The meaning of T in this case is DWORD (just try to substitute T for DWORD and see how it looks). And this will work for almost any data type. It is a little different with array though, because when serializing an array we need to specify the number of elements in the array, so that later we could retrieve the data. So we are going to write yet another operator for the STL vector class like that:
C++
template<class T>
inline mystream & operator<<(mystream & s, const vector<T>& _arr)
{
    s << (long)_arr.size();
    if (_arr.size() > 0) s.Write(&_arr.front(), (ULONG)sizeof(T)*_arr.size());
    return s;
}

template<class T>
inline mystream & operator>>(mystream & s, vector<T>& _arr)
{
    long size;
    s >> size;
    if (size > 0)
    {
        _arr.resize(size);
        s.Read(&_arr.front(), (ULONG)sizeof(T)*_arr.size());
    }
    return s; }

Same rules apply, only now as you can see the T parameter is actually a part of the vector declaration. Let's try it:
C++
mystream s("C:\\test.dat", write);
// Declare a vector of 100 integers and fill it with a value of 123
vector<int> somevector(100,123);
s << somevalue; 

So our T parameter in this case is "int", try the substitution approach I mentioned earlier and see what happens: The size of the vector is written out, than a chunk of memory with the size of the vector data type multiplied by the vector size is written out. So here you go, using templates we end up with only 4 operator overloads that cover all the basic data types plus all the possible vector data types.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)