We specialize in developing robust applications that seamlessly integrate with relational databases, as well as the cloud.

C++ Templates with Virtual Functions?

C++ offers two forms of polymorphism: virtual functions and overrides / implementations, and templates.  There can be cases when it makes the most sense to essentially have both.  You can’t do this though.  C++ expressly forbids virtual template functions because the virtual tables that would have to be built are way too complex.  Luckily, C++ offers a way around this.  It’s a programming paradigm called Policy Based Design.  In this article, I’ll cover why you might need this, and how it works.

I recently was presented with a case where I needed a class that had common operations to be performed on varying different type implementations, so I implemented it as a template.

template<typename Type>
class Loader {
public:
    Loader(Type* const value, const std::string refName);
    std::string getRefName() const;
    Type* const getType() const;
private:
    Type* value;
    std::string name;
}

This is a normal thing in C++, no big deal. I then realized though that there were a number of operations that needed to be specific for different implementations. Now, normally I might handle this by specializing the template like below:

template<typename Type>
std::string Loader::getRefName() const {
    return "Irrational Number " + name;
}

But I needed more than just specialization.  What I really needed was an interface that I could extend, and then implement the extension. The only way to do this is to have some sort of virtual function in the template that could be inherited or overrode.  But C++ forbids this, and for good reason.

There is hope though.  In 2001, a gentleman by the name of Andrei Alexandrescu wrote about a new paradigm called Policy Based Design.  The essentials are that you write your base / interface template, and have an extra template parameter for the implementer of the interface.  Then any of the methods you want to act as a method to be implemented, you write a call on the implementer for the method of that signature.

template<typename Type, typename Implementer>
class Loader {
public:
    Loader(Type* const value, const std::string refName);
    std::string getRefName() const;
    Type* const getType() const;
    Type* const load(int itemNum) {
        return impl.load(itemNum);
    }
private:
    Implementer impl;
    Type* value;
    std::string name;
}

How in the world does this work? During precompilation, the compiler adds a note to look for a method with a signature similar enough to how it was used in the method definition that at least with a cast, it can complete the call.  Then, if it finds it during compilation, it substitutes that method call in.  If not, it throws an error.  Using the example above to explain this, the compiler sees line 8, and will make a note to look in any Implementer for a method that returns the type specified as the first template parameter called load that takes an integer as its argument.

This paradigm gives a lot of extra flexibility in class design in C++.  For example, you can have multiple extensions of the interface provided by the base class.  Another example is in defining the specific implementation to use when you compile.  You don’t need this paradigm to do this, but it does make it easier.

/* LoaderConfig.h */
 
typedef ImaginaryNumberFileSystemImpl ImaginaryNumberLoaderImpl;
typedef IrrationalNumberFileSystemImpl IrrationalNumberLoaderImpl;
 /* ImaginaryNumberLoader */
 
typedef Loader<ImaginaryNumber, ImaginaryNumberLoaderImpl> IrmaginaryNumberLoader;

With this, I can have configurations for different builds for, say different systems, all by substituting out different versions of LoaderConfig.h header file. I could easily have another that uses a sockets implementation for my loaders.

As I’ve said before, there’s extra flexibility presented with this paradigm, and a lot of extra power.  You might not realize all the things this allows you to do at first, but over time, with this in your toolbox, you can start to do some pretty amazing things in C++ that developers in other languages can only dream about.