Using templates

A practical disadvantage of template classes is that they may result in generating lots of (instantiation) code. To avoid excessive code generation, we may employ generically typed wrapper classes providing safe access to an implementation employing void*. The rationale underlying such an approach is that we may use any implementation technique as long as we provide the user with a type-safe interface.

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++.)


@s lib/cell.s
slide: The cell representation of a list

An unconstrained, yet unsafe, implementation of a list is given by the definition of a cell, as depicted in slide 2-l-cell.
  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{list<E>::insert}}
void list<E>::insert(const E& el) { void* x = (void*) ⪙ if (!c) c = new cell(x); else c->insert(x); }

slide: A template list wrapper

As an example of a generic type-safe wrapper class that may be used to access the list structure, look at the definition of the template list class given in slide 2-l-list. Apart from a constructor which initializes the inner cell to zero and a destructor which destroys the inner cell, the class interface for list defines the function insert, which is used to insert references to objects of type E, and a conversion operator that delivers an instance of class iter<E>, where E is the instantiation parameter type of the list. Instances of iter<E> may be used as an iterator giving access to the elements of the list (see below).

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 cell::insert 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; };

slide: The definition of iterators

Iterators provide a convenient method to access a variety of structures in a uniform way. In the literature various styles of iterators are employed, some using explicit first, next and exist functions and others using the more concise applicative notation, as used for defining the class iter given in slide 2-l-iter.

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}
} }
Note that to obtain an iterator, that is an instance of iter, we simply employ the conversion operator for iter, which is automatically applied when assigning the list to it.

To obtain the elements of the list, a pointer to int is initialized to zero. As long as invoking iter::operator() for it (which may concisely be written as it()) results in a non-zero (pointer) value, the (de-referenced) result will be written to standard output. When it() 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; };

The operator() function

template< class E > 
\fbox{listiter::operator()}
E* listiter<E>::operator()() { void* x = p?p->el:0; if (p) p = p->next; return (E*) x;
conversion to (E*)
}

slide: The listiter class

The actual definition of both iter and listiter is somewhat complicated due to the fact that C++ employs dynamic binding only when virtual members are invoked through pointers or references.

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 iter* instance variable it.

The actual work is done by listiter. Its constructor takes a cell pointer, which is given to it when invoking the list::operator iter conversion function. To redirect the invocation of iter::operator() to the operator() function of the instance of listiter, the this pointer is given to iter when initializing iter as the base class.

The implementation of the listiter::operator() function itself is straightforward. It delivers zero whenever the cell* 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.