Template
syntax
The
template
keyword tells the compiler that the following class definition will manipulate
one or more unspecified types. At the time the object is defined, those types
must be specified so the compiler can substitute them.
Here’s
a small example to demonstrate the syntax:
//: C16:Stemp.cpp
// Simple template example
#include <iostream>
#include "../require.h"
using namespace std;
template<class T>
class Array {
enum { size = 100 };
T A[size];
public:
T& operator[](int index) {
require(index >= 0 && index < size);
return A[index];
}
};
int main() {
Array<int> ia;
Array<float> fa;
for(int i = 0; i < 20; i++) {
ia[i] = i * i;
fa[i] = float(i) * 1.414;
}
for(int j = 0; j < 20; j++)
cout << j << ": " << ia[j]
<< ", " << fa[j] << endl;
} ///:~
You
can see that it looks like a normal class except for the line
which
says that
T
is the substitution parameter, and it represents a type name. Also, you see
T
used everywhere in the class where you would normally see the specific type the
container holds.
In
Array,
elements are inserted
and
extracted with the same function, the overloaded
operator[
].
It returns a reference, so it can be used on both sides of an equal sign.
Notice that if the index is out of bounds,
require( )
function is used to print a message. This is actually a case where throwing an
exception is more appropriate, because then the class user can recover from the
error, but that topic is not covered until Chapter XX.
In
main( ),
you can see how easy it is to create
Arrays
that hold different types of objects. When you say
Array<int> ia;
Array<float> fa;
the
compiler expands the
Array
template (this is called
instantiation)
twice, to create two new
generated
classes,
which you can think of as
Array_int
and
Array_float.
(Different compilers may decorate the names in different ways.) These are
classes just like the ones you would have produced if you had performed the
substitution by hand, except that the compiler creates them for you as you
define the objects
ia
and
fa.
Also note that duplicate class definitions
are either avoided by the compiler or merged by the linker.
Non-inline
function definitions
Of
course, there are times when you’ll want to have non-inline member
function definitions. In this case, the compiler needs to see the
template
declaration before the member function definition. Here’s the above
example, modified to show the non-inline member definition:
//: C16:Stemp2.cpp
// Non-inline template example
#include "../require.h"
template<class T>
class Array {
enum { size = 100 };
T A[size];
public:
T& operator[](int index);
};
template<class T>
T& Array<T>::operator[](int index) {
require(index >= 0 && index < size,
"Index out of range");
return A[index];
}
int main() {
Array<float> fa;
fa[0] = 1.414;
} ///:~
Notice
that in the member function definition the class name is now qualified with the
template argument type:
Array<T>.
You can imagine that the compiler does indeed carry both the name and the
argument type(s) in some mangled form.
Header
files
Even
if you create non-inline function definitions, you’ll generally want to
put all declarations
and
definitions for a template in a header file. This may seem to violate the
normal header file rule of “Don’t put in anything that allocates
storage” to prevent multiple definition errors at link time, but template
definitions are special. Anything preceded by
template<...>
means the compiler won’t allocate storage for it at that point, but will
instead wait until it’s told to (by a template instantiation), and that
somewhere in the compiler and linker there’s a mechanism for removing
multiple definitions
of an identical template. So you’ll almost always put the entire template
declaration
and
definition in the header file, for ease of use.
There
are times when you may need to place the template definitions in a separate CPP
file to satisfy special needs (for example, forcing template instantiations to
exist in only a single Windows DLL file). Most compilers have some mechanism to
allow this; you’ll have to investigate your particular compiler’s
documentation to use it.
The
stack as a template
Here
is the container and iterator from
Istack.cpp,
implemented as a generic container class using templates:
//: C16:Stackt.h
// Simple stack template
#ifndef STACKT_H
#define STACKT_H
template<class T> class StacktIter; // Declare
template<class T>
class Stackt {
static const int ssize = 100;
T stack[ssize];
int top;
public:
Stackt() : top(0) { stack[top] = 0; }
void push(const T& i) {
if(top < ssize) stack[top++] = i;
}
T pop() {
return stack[top > 0 ? --top : top];
}
friend class StacktIter<T>;
};
template<class T>
class StacktIter {
Stackt<T>& s;
int index;
public:
StacktIter(Stackt<T>& is)
: s(is), index(0) {}
T& operator++() { // Prefix form
if (index < s.top - 1) index++;
return s.stack[index];
}
T& operator++(int) { // Postfix form
int returnIndex = index;
if (index < s.top - 1) index++;
return s.stack[returnIndex];
}
};
#endif // STACKT_H ///:~
Notice
that anywhere a template’s class name is referred to, it must be
accompanied by its template argument list,
as in
Stackt<T>&
s
.
You can imagine that internally, the arguments in the template argument list
are also being mangled to produce a unique class name for each template
instantiation.
Also
notice that a template makes certain assumptions about the objects it is
holding. For example,
Stackt
assumes there is some sort of assignment operation for
T
inside the
push( )
function. You could say that a template “implies an interface” for
the types it is capable of holding.
Here’s
the revised example to test the template:
//: C16:Stackt.cpp
// Test simple stack template
#include <iostream>
#include "../require.h"
#include "Stackt.h"
using namespace std;
// For interest, generate Fibonacci numbers:
int fibonacci(int n) {
const int sz = 100;
require(n < sz);
static int f[sz]; // Initialized to zero
f[0] = f[1] = 1;
// Scan for unfilled array elements:
int i;
for(i = 0; i < sz; i++)
if(f[i] == 0) break;
while(i <= n) {
f[i] = f[i-1] + f[i-2];
i++;
}
return f[n];
}
int main() {
Stackt<int> is;
for(int i = 0; i < 20; i++)
is.push(fibonacci(i));
// Traverse with an iterator:
StacktIter<int> it(is);
for(int j = 0; j < 20; j++)
cout << it++ << endl;
for(int k = 0; k < 20; k++)
cout << is.pop() << endl;
} ///:~
The
only difference is in the creation of
is
and
it:
You specify the type of object the stack and iterator should hold inside the
template argument list.
Constants
in templates
Template
arguments are not restricted to class types; you can also use built-in types.
The values of these arguments then become compile-time constants for that
particular instantiation of the template. You can even use default values for
these arguments:
//: C16:Mblock.cpp
// Built-in types in templates
#include <iostream>
#include "../require.h"
using namespace std;
template<class T, int size = 100>
class Mblock {
T array[size];
public:
T& operator[](int index) {
require(index >= 0 && index < size);
return array[index];
}
};
class Number {
float f;
public:
Number(float ff = 0.0f) : f(ff) {}
Number& operator=(const Number& n) {
f = n.f;
return *this;
}
operator float() const { return f; }
friend ostream&
operator<<(ostream& os, const Number& x) {
return os << x.f;
}
};
template<class T, int sz = 20>
class Holder {
Mblock<T, sz>* np;
public:
Holder() : np(0) {}
T& operator[](int i) {
require(i >= 0 && i < sz);
if(!np) np = new Mblock<T, sz>;
return np->operator[](i);
}
};
int main() {
Holder<Number, 20> h;
for(int i = 0; i < 20; i++)
h[i] = i;
for(int j = 0; j < 20; j++)
cout << h[j] << endl;
} ///:~
Class
Mblock
is a checked array of objects; you cannot index out of bounds. (Again, the
exception approach in Chapter 16 may be more appropriate than
assert( )
in this situation.)
The
class
Holder
is much like
Mblock
except that it has a pointer to an
Mblock
instead of an embedded object of type
Mblock.
This pointer is not initialized in the constructor; the initialization is
delayed until the first access. You might use a technique like this if you are
creating a lot of objects, but not accessing them all, and want to save storage.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru