Canonical Class Template
We can modify the listing above to turn it into a canonical template class definition. Just like function templates, this means we have to use the <T> template syntax in a few places, and sometimes in more than a few.
Luckily, it's not that hard, and the result can be seen in the following listing:
template <class T> class TBase
{
public:
// Constructors & Destructors
TBase(void);
TBase(const TBase<T>& copy);
virtual ~TBase(void);
// Operator overloading
TBase<T>& operator = (const TBase<T>& other);
int operator == (const TBase<T>& other) const;
// Output
friend ostream& operator << (ostream& os, const TBase<T>& other);
};
Just to let you know what the implementation looks like (the empty skeletons, that is), take a look at the following listing:
// Constructors & Destructors
template <class T> TBase<T>::TBase(void) {}
template <class T> TBase<T>::TBase(const TBase<T>& copy) {}
template <class T> TBase<T>::~TBase(void) {}
// Operator overloading
template <class T> TBase<T>& TBase<T>::operator = (const TBase<T>& other) {}
template <class T> int TBase<T>::operator == (const TBase<T>& other) const {}
// Output
template <class T> ostream& operator << (ostream& os, const TBase<T>& other) {}
This is usually the place where I could do with a little help or support to get the class template syntax right.
Derived Templates
If you've been able to keep up with me so far, then let's get to the final round: templates derived from other templates. Sometimes you just have to derive your own custom class template TDerived from a base template class TBase (sound familiar?).
And just for your amusement (and mine), I've included the header listing for the derived canonical class template definition below:
template <class T> class TDerived: public TBase<T>
{
public:
// Constructors & Destructors
TDerived(void);
TDerived(const TDerived<T>& copy);
virtual ~TDerived(void);
// Operator overloading
TDerived<T>& operator = (const TDerived<T>& other);
int operator == (const TDerived<T>& other) const;
// Output
friend ostream& operator << (ostream& os, const TDerived<T>& other);
};
Certainly this TDerived class template definition needs a list of empty implementation skeletons, which are defined as follows (empty because they're skeletons, but they still need to be implemented by the programmer, of course).
// Constructors & Destructors
template <class T> TDerived<T>::TDerived(void): TBase<T>() {}
template <class T> TDerived<T>::TDerived(const TDerived<T>& copy): TBase<T>(copy) {}
template <class T> TDerived<T>::~TDerived(void) {}
// Operator overloading
template <class T> TDerived<T>& TDerived<T>::operator = (const TDerived<T>& other) {}
template <class T> int TDerived<T>::operator == (const TDerived<T>& other) const {}
// Output
template <class T> ostream& operator << (ostream& os, const TDerived<T>& other) {}
OK, who could already produce the above listing without a second thought? If you could, then you probably didn't need to read this article, because the fun stuff is over. What remains is the description of a little tool that I made for myself to actually produce and generate the output listings that we've seen so far.