Templates

Terminology

The reader may well be familiar with this terminology, but they are defined anyway to minimise confusion.

  • Compile Dependency We say that class A is dependent on B if recompiling B requires recompiling A. For example, derived classes are dependent on their base classes. Note that this is not quite the same as a "knows-a" relationship. You can know-a class without there being a compile time dependency. However, in the case of template classes, it's often true that knowing a parametrisation of a template class (for example, foo<int>) is the same as having a compile-time dependency on it.
  • Include guard Preprocessor directives designed to prevent a header from being included more than once, in violation of the one definition rule.

    Function definitions go in the same file as the class declaration

    A common newbie mistake is to think that function definitions and class declarations should go in seperate files, say class.h and class.cc. This is the way it works for an ordinary class, but this is not the way it works for templates. Template code is expanded at compile time, so the required template classes must be known at that stage. If that sounds awfully static, well, it is. For more dynamic behaviour, one uses inheritance.

    In a template class, all the code goes into a .hpp file. Unlike other classes, one (usually) does not have a seperate header and source code file. Everything goes in the header file.

    There are exceptions to this rule, and indeed the standard supports ways around this (such as export), but most implementations don't. So the only moderately portable way to do it is with the .hpp file.

    You can think of a template class as a "code factory". Each time part of your code requires a template with a certain parameter, it just cranks out the appropriate parametrisation. Each parametrisation is a seperate class in its own right. This obviously has the potential to lead to a lot of code bloat if you're not careful! It can also lead to a lot of compile time overhead, since it's possible that a template parametrisation will be expanded in several different translation units. ( note: for templates, it's OK for the same implementation to expand the same template code in several different translation units, indeed it's the default behaviour in some implementations. In this case, the linker will typically know how to "collapse" the several duplicate versions. Note that this is only true for implicit instantiations. For example, the implementation sees a request for myClass<int> and chooses to expand it. It is not true for explicit specialisations. We will discuss specialisation later. )

    Note that each parametrisation has no implicit sibling relationship with different parametrisations. So it's not necessarily true that class Foo<int> is a sibling class of class Foo<double> Another way of saying this is that:

    The template class, unlike inheritance, does not provide run time polymorphism.
    This begs the question -- what if you want them to be siblings ? We address this in the next section.

    Writing specialisations

    Where writing specialisations gets really tricky is where the specialisations are in different translation units to the general definitions. Here's an example

    //-------------------------------------------- foo.hpp
    #ifndef FOO_H
    #define FOO_H
    #include <iostream>
    template<class TYPE>
    class foo
    {
    public:
        foo(){}
        void print();
    };
    
    template<class TYPE> 
    void foo<TYPE>::print()
    {
        std::cout << "Unknown type" << std::endl;
    }
    
    #endif
    //---------------------------------------- end foo.hpp
    
    //--------------------------------------------- foo.cc
    #include <foo.hpp>
    template<>
    void foo<int>::print()
    {
        std::cout << "int" << std::endl;
    }
    //----------------------------------------- end foo.cc
    
    //-------------------------------------------- main.cc
    #include <foo.hpp>
    int main()
    {
        foo<int> x;
        x.print();
    }
    //---------------------------------------- end main.cc
    

    The problem with this code is that when main.cc includes foo.h, it may use the definition in foo.h to create the foo<int> object, and ignore the specialisations (in fact it almost certainly will if for example foo.o is linked into a shared library.)

    One idea (perhaps the obvious thing to do) would be to put the specialisation code in foo.h. The problem with this is that it will produce linking errors, because it violates the "one definition rule". The include guards would prevent the same code from being duplicated in a single translation unit, but the problem arises when one attempts to link duplicate code in distinct translation units.

    Another idea would be to simply not offer a default implementation. However, the programmer is required to provide one (and with good reason!) so that is not an option.

    Another idea is to find a means to tell the compiler that we want to specialise the appropriate member functions, and don't want them instantiated. The way to do this is to forward declare the specialisations, in the format shown below. We do this by declaring the specialiation in advance, as a way of instructing the compiler that we will be providing a specialisation, and hence the compiler should not manufacture one from the default template. Here's the new listing, foo.new.h that results.

    // foo.new.h
    
    #ifndef FOO_H
    #define FOO_H
    #include <iostream>
    template<class TYPE>
    class foo
    {
        public:
            foo(){}
            void print();
    };
    
    template<> void foo<int>::print();
    
    template <class TYPE>
    void foo<TYPE>::print()
    {
        std::cout << "Unkown type" << std::endl;
    }
    #endif
    
    // foo.new.h
    

    Creating a common base class for different parametrisations

    A common misconception is that template classes with different parameters are somehow related by inheritance, but in fact this is not true. For example, the class foo<int> is not related (in the sense of inheritance) to foo<float> and it is certainly not true that there's a base class called foo from which the parametrised classes are derived. So if we want a base class for our template classes, we need to create one.

    Creating such base classes is often useful when we wish to dynamically create templated objects, but decide the parameters at run time. Template classes are created statically at compile time. However, by creating a base class, we can defer decisions about which template object to instantiate until run time. For example, here's how one could implement a very simple virtual constructor idiom with different templates:

    base.h

    // This class is abstract.
    class Base
    {
    public:
        // put some code here
        static Base* make (int x);
    protected:
        Base();
        Base(const Base&);
        Base& operator=(const Base&);
        virtual void ~Base() = 0; // note: must provide an implementation
    };
    
    
    

    base.cpp

    
    Base* Base::make(int x)
    {
        Base* res = 0;
        switch(x)
        {
            case 0:
                res = new Derived<char>;
                break;
            case 1: 
                res = new Derived<short>;
                break;
            case 2:
                res = new Derived<int>;
                break;
        }
        return res;
    
    }
    

    derived.hpp

    template<class value_type>
    class Derived 
    : public Base
    {
        // put some more code here
    };
    
    
    
    This is still quite messy though -- since Base instantiates these objects, the header needs to include the Derived class code, and barring special directives to suppress compilation of derived classes, there's some waste and unnecessary dependency (modifying the derived class will possibly require recompiling the base). Not only are bidirectional dependencies ugly and annoying, things start to get really nasty when you have several different classes depending on template classes.
    Avoid compile-dependencies on template classes. They lead to proliferation of dependencies and compile time overhead.
    It's better to create a dedicated
    factory object to handle creation instead of putting it into the base class.

    Template members aren't virtual ... or are they ?

    One thing that has caused me some degree of frustration has been the fact that template member functions cannot be virtal. That is, the following code is not allowed.

    	class foo {
    		// .....
    		template<class T> virtual void bar();
    	};
    

    The reason why this is not allowed is that the vtable needs to be created when the class is instantiated by the compiler.

    There are two problems that one may wish to solve with virtual template members:

    1. It's possible that we would want to dispatch an overloaded virtual function to a template function, because the implementations of all the different overloaded functions are the same. The problem is that if a method is virtual in the parent class, it also must be virtual in the derived class.
    2. We might not be wiling to make a commitment (even at compile time) about what types we ``support'', and so we use a template as a way to dispatch ``any'' type. In other words, we want a ``flexible'' virtual function. A common mistake made by newbies is to attempt to use templates to directly solve this problem. However, this is not the right way to do it. We will discuss further.

    Solutions (1)

    The first problem has a simple solution, provided that the arguments are not dependent on the template member function.

    	class Base 
    	{
    	public:
    		virtual void do_this () = 0;
    
    	};
    	class Derived : public Base
    	{
    	public:
    		virtual void do_this() { do_this_impl(); }
    
    	private:
    		template<class TYPE> void do_this_impl()
    
    
    	};
    	

    There's also a simple solution if we know in advance what template parameters we need. However, this is a little inelegant because we need to hard code all possible template parameters.

    
    	class Base 
    	{
    	public:
    		virtual void do_this (char) =0;
    		virtual void do_this (short) =0;
    		virtual void do_this (int) =0;
    		virtual void do_this (float) =0;
    
    	};
    	class Derived : public Base
    	{
    	public:
    		virtual void do_this(char x) { do_this_impl(x); }
    		virtual void do_this(short x) { do_this_impl(x); }
    		virtual void do_this(int x) { do_this_impl(x); }
    		virtual void do_this(float x) { do_this_impl(x); }
    
    	private:
    		template<class TYPE> void do_this_impl(TYPE)
    
    	};
    
    

    Solutions (2)

    We now discuss the second problem. One of the problems with the above approach is that we had to specify all possible template parameters in more than one place. This is because templates and inheritance really don't mix that well. (templates are more "static" in their nature).

    One question worth asking oneself is

    Why are you using templates in the first place ?
    They are usually used to enforce stricter compile time checking (for example, you can't insert a char into list<int> ) but sometimes it's just a nuisance. In this example, it's not leading us to less error prone code, because it's forcing us to do a lot of unnecessary hard coding.

    An alternative approach would be to handle problems of dynamic polymorphism with inheritance (which is designed to deal with that sort of thing) and leave templates (which are not designed to deal with that sort of thing) out of the picture. But there's still some problems:

    The answers :

    Having made a case against templates, it's worth mentioning that templates possibily do have a legitimate role in this scheme, as helpers to implement functionality in derived classes. This is a more appropriate use of templates -- using them as ``code factories'' to avoid writing cut-and-paste code. This would leave open questions about how (or if) one would add support for new template classes at runtime. It may be possible using the base class for the template outlined previously, but I'm personally not sure if that's a good idea. One is rarely sure until one tries coding it. Yeah, I'll do it soon ;-) Meanwhile, the interested reader is free to try!

    In fact the example in the envelope section covers how one can combine registration with the envelope/letter idiom.

    Bibliography

  • Dynamic class loading for C++ on Linux , James Norton, Linux Journal, May 2000