template<class type> class stack {\fbox{stack}
public: stack( int n ) { top = -1; impl = new type[n]; } ~stack() { delete[] impl; } bool empty() { return top == -1; } void push( type it ) { impl[++top] = it; } type pop() { return impl[top--]; } private: int top; type* impl; };
Another, very important, difference is illustrated by the code fragment showing
the use of a (template) stack
stack<char> s(100);
s.push('+'); s.push('+'); s.push('c');
while ( !s.empty() ) {
cout << s.pop();
}
When creating the stack, the user must explicitly indicate
what type of elements the stack contains.
Other uses of the stack do not require any explicit type conversions
and are completely type checked by the compiler.
Note that the template construct actually adds to the power
of the language, since a stack of void pointers may easily
be defined by giving the right instantiation parameters.
Using templates
We will conclude this section on generic types by looking at a type-safe generic list class, employing void to represent the actual list structure. (This example involves some rather complex features of C++.)
template< class E >\fbox{list<E>}
class list { friend class listiter<E>; public: list() { c = 0; } ~list() { if(c) delete c; } void insert(const E& el); operator iter<E>() { return listiter<E>(c); }(*)
private: cell* c; }; template< class E >\c{\fbox{}}
void list<E>::insert(const E& el) { void* x = (void*) ⪙ if (!c) c = new cell(x); else c->insert(x); }
For inserting an element, we must convert the typed reference into a void pointer by taking the address of the argument of insert. We then create a new cell if the inner cell is still zero and employ otherwise.
template< class E >\fbox{iter}
class iter { public: iter(iter* x) : it(x) {} virtual E* operator()() { return (*it)(); }\c{// indirect}
private: iter<E>* it; };
Below, an example is given of how an iterator
may be used to traverse the list
void main() {
list<int> lst;
lst.insert(1); lst.insert(2);
iter<int> it = lst;
\c{// get the iterator}
int* p = 0; start
while ( p = it() ) {
cout << "item;" << *p << endl; \c{// take the value}
}
}
To obtain the elements of the list, a pointer to int is initialized to zero. As long as invoking for it (which may concisely be written as ) results in a non-zero (pointer) value, the (de-referenced) result will be written to standard output. When produces a zero pointer value, we have reached the end of the list.
template< class E >\fbox{listiter}
class listiter : public iter<E> { public: listiter( cell* c ) : iter<E>(this), p(c) {}virtuality
~listiter() { cout << "~listiter" << endl; } E* operator()();the iterator function
private: cell* p; };
template< class E >\fbox{}
E* listiter<E>::operator()() { void* x = p?p->el:0; if (p) p = p->next; return (E*) x;conversion to (E*)
}
The constructor for iter expects an instance of iter as a parameter. The iter::operator() function in its turn invokes the operator() function for the instance variable it.
The actual work is done by listiter. Its constructor takes a cell pointer, which is given to it when invoking the conversion function. To redirect the invocation of to the operator() function of the instance of listiter, the this pointer is given to when initializing as the base class.
The implementation of the function itself is straightforward. It delivers zero whenever the instance variable is zero. Otherwise it delivers the element of the cell, which is converted to the proper type and sets the cell pointer to the next element in the list.