what is the difference between templates and polymorphism
In a nutshell, it comes down to the outcome of your commonality-and-variability analysis of the problem to be solved.
If you're doing the same operations on different stuff, then you would use templates e.g. List<int>
and List<float>
If you're doing the same operations in different ways on the same stuff according to the context, which also has some commonality, then you would use polymorphism e.g. the AbstractInterface::openFile
interface with the derivatives WindowsInterface::openFile
and LinuxInterface::openFile
, one of which would be used according to the context. The abstract interface identifies the commonality of the fact that the same operation is being done conceptually, the derivatives fulfil the variability of the implementation of the concept.
You seem to misunderstand what polymorphism is.
Polymorphism, at its core, has nothing to do with derived classes. Polymorphism simply means the ability to use a type without knowing everything about it. Rather than using a concrete type, polymorphism relies on some form of prototype to define what types it takes. Any types that fit that prototype are accepted.
Runtime polymorphism, in C++, is provided by deriving classes from a base class that contains virtual functions. The base class and virtual functions form the polymorphic prototype. Code written to accept the base class that calls these virtual functions will accept any class instance derived from the base class.
Compile-time polymorphism is polymorphism that happens... at compile time ;) What this means is that the compiler must know what is going on. You may have written the C++ code against a polymorphic prototype, but the compiler doesn't care. You get specific concrete types post-compilation.
Compile-time polymorphism is provided by templates in C++. A template function or class can take any type which conforms to a prototype, usually called a "concept". Unlike base classes and virtual functions, the prototype is implicit: the prototype is defined only by how the type is used by the template function/class.
If you have this template function:
template<typename T>
void Stuff(T &t)
{
t.call(15);
}
There is an implicit requirement on T
. This requirement is that it has a member function called call
. There must be a single overload of this member function which can be called with an integer value.
This means that any type that happens to fit this prototype can be used.
Template polymorphism is more broad than inheritance polymorphism, because it can be used by a broader array of types. A type has to be designed specifically to use inheritance polymorphism; you have to derive from a class. A type can be non-destructively (ie: you don't have to change the type itself) adapted to template polymorphism. Even moreso if your template prototype is well designed:
template<typename T>
void Stuff(T &t)
{
call(t, 15);
}
All that this version of Stuff
requires is that there is some function that takes a T&
and an integer value. If I have some type that I want to use with Stuff
, all I have to do is define a call
function in an appropriate namespace (namely, the namespace that the type was defined in). And this will work just fine. All without modifying the type itself.
Of course, compile-time polymorphism is... compile-time. If I want some user input or data file to select the polymorphic type, templates aren't going to help a whole lot (though type erasure, a template-based technique, can help). The principle benefit of runtime polymorphism is that it is indeed runtime.
Another benefit is that it is more precise about its prototypes. Everything is explicitly stated about inheritance. The virtual function interface in a base class is clearly laid out. The compiler will stop you from attempting to use that base class incorrectly (calling methods that don't exist on it). Indeed, a decent IDE will guide your code so that you will only see the methods on the base class.
Template polymorphism is a lot more implicit. Since C++ has no way of spelling out the prototype that a particular template function/class puts on a type, it's very easy to accidentally call something on a template type that you shouldn't. The compiler will only detect this when you try to use a type that doesn't fit the prototype. And even then you will generally get a massive error spew (depending on how deeply nested your template code is) that makes it difficult to know where the problem is.
It's also a lot harder to implement the implicit template polymorphic prototype, since it isn't spelled out. Implementing a derived class requires walking through the base class, looking at all of the virtual functions, and implementing them. Doing this for a template prototype is much more difficult, unless there is documentation somewhere that spells it out. If you fail to implement something, you again get an error spew that is generally less than forthcoming about the problem.