Prior to templates, type-agnostic C++ code had to be written using runtime polymorphism. But with templates as well, you can combine the two techniques.
For example, suppose you wanted to store values of any type, for later retrieval. Without templates, you'd have to do this:
struct PrintableThing
{
// declare abstract operations needed on the type
virtual void print(std::ostream &os) = 0;
// polymorphic base class needs virtual destructor
virtual ~PrintableThing() {}
};
class PrintableContainer
{
PrintableThing *printableThing;
public:
// various other secret stuff
void store(PrintableThing *p);
};
The user of this library would have to write their own derived version of PrintableThing by hand to wrap around their own data and implement the print function on it.
But you can wrap a template-based layer around such a system:
template <T>
struct PrintableType : PrintableThing
{
T instance;
virtual void print(std::ostream &os)
{ os << instance; }
PrintableType(const T &i)
: instance(i) {}
};
And also add a method in the header of the library, in the declaration of the PrintableContainer class:
template <class T>
void store(const T &p)
{
store(new PrintableType(p));
}
This acts as the bridge between templates and runtime polymorphism, compile-time binding to the << operator to implement print, and to the copy-constructor also (and of course also forwarding to the nested instance's destructor).
In this way, you can write a library entirely based on runtime polymorphism, with the implementation capable of being hidden away in the source of the library, but with a little bit of template "sugar" added to make it convenient to use.
Whether this is worth the trouble will depend on your needs. It has a purely technical benefit in that runtime polymorphism is sometimes exactly what you need, in itself. On the downside, you will undoubtedly reduce the compiler's ability to inline effectively. On the upside, your compile times and binary code bloat may go down.
Examples are std::tr1::function and boost::any, which have a very clean, modern C++ template-based front end but work behind the scenes as runtime polymorphic containers.