The reader may well be familiar with this terminology, but they are defined anyway to minimise confusion.
class Foo { int x; public: Foo(); };
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.
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 |
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.cppBase* 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.hpptemplate<class value_type> class Derived : public Base { // put some more code here }; |
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.
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:
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) };
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.