Template Functions, Part 2

Posted by Matt | Filed under , ,

In a previous post, I gave you the basics for template functions in C++.  One of the issues I find when I have a problem and am looking for examples of the solution is that many times the examples are so basic that they don’t really demonstrate the solution.  Today, I want to show you a complex solution that I had to create using templates.  Hopefully this will give you a better idea of what we can do.

In my real-world case, I had a potential file system of files and a database.  Files had to be defined in a data structure (read from Xml), traversed and put into a database.  The database contained different table definitions for different types of files (binary files, document files, etc.).

At first, the data structures looked like this:

class CBinaryFile
{
public:
  // Data

  typedef CList<CBinaryFile> List;
};

class CContentFile
{
public:
  // Data

  typedef CList<CContentFile> List;
};

class CFolder
{
public:
  CBinaryFile::List BinaryFiles;
  CContentFile::List ContentFiles;

  typedef CList<CFolder> List;

  CFolder::List Folders;
};

CFolder::List TheFolders;

You’ll notice that this is a recursive list of Folders that contains binary files and content files.  However, additional requirements came into play:  in memory, the binary file hierarchy needed to be separated from the content file hierarchy.  This means that I needed to have one set of folders for binary files and a different set of folders for content files.  I could have very well kept the data structure the same, but just had two top-level variables for the folders:

CFolder::List BinaryFolders;
CFolder::List ContentFolders;

Using this, I’d basically have wasted memory.  No one likes that :)

So I changed to use templates.  My CFolder class changed to this:

template <class T>
class CFolder
{
  T::List Files;

  typedef CList< CFolder<T> > List;

  CFolder::List Folders;
};

This way, my two top-level variables become:

CFolder<BinaryFile>::List BinaryFolders;
CFolder<ContentFile>::List BinaryFolders;

So far though, all I’ve shown you was class templates.  Where does the function template come into the picture?

I now need to traverse the folders and process the files into the database.  I did not want to add the functions to the class.  I’ll explain later.

void Traverse(CFolder<BinaryFile>::List &folders)
{
  // ...
}

void Traverse(CFolder<ContentFile>::List &folders)
{
  // ...
}

Again, duplication of code.  And we don’t want that either.  So I did this:

template <class T>
void Traverse(CFolder<T>::List &folders)
{
  // ...
}

This gave me a single function that does the work of traversing the folders.  But what about the end?  The 2 classes needed to be processed differently.  For this, I created an abstract item handler class that was implemented for each file type.  Here is the final code:

class CBinaryFile
{
public:
  // Data

  typedef CList<CBinaryFile> List;
};

class CContentFile
{
public:
  // Data

  typedef CList<CContentFile> List;
};

template <class T>
class CFolder
{
public:
  typename T::List Files;
	
  typedef CList< CFolder<T> > List;

  List Folders;
};

template<class T>
class IItemHandler
{
public:
  virtual void ProcessItem(T &item) = 0;
};

class BinaryFileHandler : public IItemHandler<CBinaryFile>
{
public:
  void ProcessItem(CBinaryFile &item)
  {
    // Insert into the database
  }
};

class ContentFileHandler : public IItemHandler<CContentFile>
{
public:
  void ProcessItem(CContentFile &item)
  {
    // Insert into the database
  }
};

template <class T>
void Traverse(typename CFolder<T>::List &folders, 
    IItemHandler<T> *pHandler)
{
  POSITION pos = folders.GetHeadPosition();
  while (pos != NULL)
  {
    CFolder<T> &folder = folders.GetNext(pos);

    // Process our items
    POSITION p2 = folder.Items.GetHeadPosition();
    while (p2 != NULL)
    {
      T &item = folder.Items.GetNext(pos);
      pHandler->ProcessItem(item);
    }

    // Do the sub-folders
    Traverse(folder.Folders, pHandler);
  }
}

BinaryFileHandler bfh;
Traverse(binaryFolders, &bfh);

ContentFileHandler cfh;
Traverse(contentFolders, &cfh);

Sometimes, it seems like alot of work and code just to avoid duplicating code or wasting memory.  These are judgement calls. The nice thing is that if I needed to add another file type later on, the groundwork is already done.  All I need to do is define my file type class and then create the handler class to insert it into the database.  Obviously, the more file types that are needed, the better looking this solution is.  It’s questionable whether this work is beneficial for only 2 file types.  But if you had 25 or 50, I think it’s obvious that this solution is better than code duplication.  Especially if you find a bug in the traversal code :)

Template Functions

Posted by Matt | Filed under , ,

Many people know about template classes in C++.  When you start working with templates, this is usually what you’re looking for.  The staple of template demonstrations is the linked list class.  This one is a common need from developers.  The good news is that the STL libraries implement this one very well.  Anyways, what many people don’t know about templates is that you can create template functions just as easily.  Sometimes, you may not even know you’re calling a template function.

Let’s create a min function.  It’s common to use macros, but macros don’t always maintain type-safeness.

template <class T>
T Min(T a, T b)
{
  if (b < a)
    return b;
  else
    return a;
}

You’ll notice that the first line is the same as when you’re creating a template class.  And just like a template class, you just reference your template parameter normally.

However, now, we can write code like this:

int a = 5;
int b = 6;
int m = Min(a, b);

This will generate a function Min using int as a base type.  And it’s type-safe.  You can easily change it to:

double a = 5;
double b = 6;
double m = Min(a, b);

This will call a double version of the same function.  If you wanted to do this without templates, you’d have to either (a) use macros, or (b) create 2 different functions.

The following code will generate an error:

int a = 5;
double b = 6;
int m = Min(a, b);

“error C2782: 'T Min(T,T)' : template parameter 'T' is ambiguous”

To fix the above issue, you can call Min as this instead:

int m = Min<int>(a, b);

This will disambiguate the parameters giving you a solid definition.  Note that b will be casted to an int as the call commences.

You get the idea now.  This is a very basic example.  I always hate it when you needs to see how to use something, but they always give very basic examples which don’t show the solution to the problem you’re having.  In an upcoming post, I’ll show you a much more complex solution using templates that I actually needed to develop.