Template
Basics
Using template syntax,
we can create an Array class that is parameterized on any
number of parameters, including the type of data to manage and how
large the internal buffer should be:
template <typename T, size_t MAX_ELEMS>
class Array {
public:
T& operator[](size_t n) {
if( n < 0 || n >= MAX_ELEMS ) throw "out of bounds!";
return m_rg[n];
}
protected:
T m_rg[MAX_ELEMS];
};
Notice that the only difference in this code is
the use of the template statement before the class
declaration. When the compiler sees a template, it knows to store
the class declaration in its internal data structures, but not to
generate any code. The compiler can't generate the code until it
sees how the client would like to use it:
struct Point { long x; long y; };
void main(int argc, char* argv[]) {
Array<long, 8> a1; // Array of 8 longs
Array<char, 256> a2; // Array of 256 chars
Array<Point, 16> a3; // Array of 16 Points
...
}
The compiler uses the client's template
parameters to generate the code for the class on demand,
effectively creating a new member of the Array family of
classes with each use. Because the compiler is using the
template parameters to generate the code, only parameters whose
values are known at compile time are allowed. However, that includes built-in types, user-defined
types, constants, and even function pointers. To make the template
even more convenient for the client to use, you're allowed to
declare default values for template parameters, just as you would
for functions:
template <typename T, size_t MAX_ELEMS = 8>
class Array {...};
void main(int argc, char* argv[]) {
Array<long> a1; // Array of 8 longs
Array<char, 256> a2; // Array of 256 chars
...
}
Template
Specialization
You might decide that for a specific combination
of template parameters, the generic template expansion isn't good
enough. For example, if you decide that an Array of 256
characters should have an equality operator, you might decide to
override the Array general template using the template
specialization syntax:
template <> // No template arguments here
class Array<char, 256> { // Template argument values here
public:
// You are not required to provide the same functionality
// as the general template (although it's a good idea)
char& operator[](size_t n) {
if( n < 0 || n >= 256 ) throw "out of bounds!";
return m_sz[n];
}
// You may add functionality not in the general template
bool operator==(const Array<char, 256>& rhs) {
return strcmp(m_sz, rhs.m_sz);
}
protected:
char m_sz[256];
};
The client doesn't have to do anything new to
use the specialized version of the template. When the compiler sees
Array<char, 256>, the client automatically gets the
specialized version.
Templates as Base
Classes
Template specialization allows the addition of new
functionality and optimized implementations based on specific
template parameters. For example, the specialization I just showed
specializes on all the parameters, both the type and the size of
the array. It would probably be more useful to be able to
specialize on the type of data held, but to expose additional
functions for character strings of any size. This can be
accomplished by using the Array as a base class:
template <size_t MAX_LEN>
class String : public Array<char, MAX_LEN+1> {
public:
// Additional functionality
bool operator==(const String<MAX_LEN>& rhs) {
return strcmp(m_rg, rhs.m_rg);
}
};
Notice that the String is still
parameterized on length and that it passes arguments to the base
Array class. The type is fixed because we're building a
string, but the number of elements to store is based on the
String template argument. In effect, this achieves a
partial specialization, although the client code must use the
String template instead of the Array template to
make use of it.
|