The $void$ pointer

Employing void pointers allows for the definition of unconstrained generic types, although in a rather crude way. To understand how that works, it is necessary to reflect on the meaning of a type system. One important aspect of a type system is that it may protect the programmer from a number of common errors, ranging from trivial typos to inconsistent structures. In particular, when the type system supports subtyping, the compiler may check whether the actual relation satisfies the subtyping relation. A type gives information concerning the object to which a variable or expression refers. The more specific a type, the more a compiler needs to know in order to assist the programmer in specifying correct programs. From this perspective, the void pointer figures as the top of the type hierarchy, since the compiler cannot be assumed to have any knowledge concerning its (correct) use. Consequently, the compiler leaves the responsibility entirely to the user, who may convert the void pointer to any type at will. Because of this absence of type information, the void pointer may indeed be called a generic type. An example of employing the void pointer to define a generic stack is given in slide 2-void.
  typedef void* type;  
generic void*
class stack {
\fbox{stack}
public: stack( int n = 12 ) { 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; };

slide: Using the void pointer

For storing the contents of the stack, an array of void pointers is created when evaluating the constructor, which is deleted when the destructor is called. Provisions for dynamically enlarging the size of the stack and for testing its bounds have been omitted, but this is easily provided for.

An example of using the stack may look as follows:


  stack s(100);
  char plus = '+'; char c = 'c';
  s.push(&plus); s.push(&plus); s.push(&c);
  while ( !s.empty() ) {
    		cout << *(char*) s.pop();
    		}
  
To retrieve a value, first the pointer must be cast to a pointer of the appropriate type, and then it may be de-referenced to deliver the actual value. This code clearly illustrates that the user is entirely responsible for correctly using the stack. Now when we look at the code, to push elements on the stack, it is sufficient to take the address of the value inserted. However, when removing elements from the stack, the user must know precisely what the type of the element popped is. In the example, this first requires the conversion of the void pointer to a char pointer, and then a de-reference with an explicit cast to char. Evidently, generic types of this kind are error-prone, not to say ugly.