Example code from lecture...
When you apply a template to an entire class, there are two issues to always keep in mind and one which may creep up.
The name of the type you are creating is no longer just the class name but rather the class name augmented with the instantiation type:
template <typename TypeT> class ClassName;
This does not declare the type ClassName but rather the type ClassName<TypeT>. When you create an object of this type, TypeT will be filled in with the explicitly specified type, of course:
ClassName<double> my_obj;
But even when defining ClassName functions out-of-line, they will need to specify the full type name properly. Notice the three places this happens in a sample operator=:
template <typename TypeT> ClassName<TypeT> & ClassName<TypeT>::operator=(const ClassName<TypeT> & cob) { // code to implement op= for this class... return *this; }
Note that a constructor or destructor is a function — not a type and so would merely be the name of your class (ClassName):
template <typename TypeT> ClassName<TypeT>::ClassName(const ClassName<TypeT> & cob) // init list here { // copy ctor code here }
You do not need to use this fully qualified type name while inside the class definition. There, it is assumed that you are defining things related to the class whose very definition you are in:
template <typename TypeT> class ClassName { public: ClassName & operator=(const ClassName & cob); ClassName(const ClassName & cob); ClassName other_func(const ClassName & cob) const; };
In order to split the class' definitions into separate files (as for a proper library), you must currently perform a bit of pre-processor trickery. Start by taking the would-be implementation file — except for the #include'ing of the interface from the top — and place that in a separate file. I name mine with a .def extension for definitions. Other extensions I've seen used are .tem, .tmpl, .tpl, and .dfn. (Try to avoid an extension like .tmp, however, since that is likely to be construed as a temporary file and erased automatically by the system on a periodic basis.)
This new file will be then #include'd into both the old implementation file and the interface file surrounded by complementary #if?def's. The interface file would contain:
#if defined(TEMPL_CANT_SEP) #include "my_templ_lib.def" #endif
And the implementation file would merely be:
#if !defined(TEMPL_CANT_SEP) #include "my_templ_lib.h" #include "my_templ_lib.def" #endif
The logics of these along with a proper #define or -D command-line argument (or similar GUI environment dialog) to properly set the symbol TEMPL_CANT_SEP will keep the implementation file empty and the interface file effectively containing the template's definitions while being obviously devoid of them at the same time. (Freaky, no?)
How do we set up this TEMPL_CANT_SEP symbol, did you say? Well, we might have this in a source file needing to use the template'd class:
#define TEMPL_CANT_SEP #include "my_templ_lib.h"
Or, from the command-line while compiling:
$ CPP -DTEMPL_CANT_SEP app other_lib . . . $ ./app.out . . .
(Note that you don't need to mention the template class' library on the compile line since its implementation file is effectively empty when employing the -DTEMPL_CANT_SEP definition...)
template'd classes have trouble making friends, too. There is a three step process involved with this:
declare the template'd class
declare the function/class that is to be the template'd class' friend
declare the friendship inside the template'd class' definition as normal except that you must place empty angle brackets — <> — between the friend's name and its argument list
The worst occurrence of this is making operator<< a friend because you'll end up with three < in a row:
template <typename TypeT> class ClassName; template <typename TypeT> ostream & operator<<(ostream & os, const ClassName<TypeT> & cob); template <typename TypeT> class ClassName { friend ostream & operator << <> ( ostream & os, const ClassName & cob); };
Here also find the example from lecture...