Polymake Template Library (PTL): Generic and Persistent Types
Polymake Template Library (PTL)  4.2
Generic and Persistent Types

Many of the PTL container classes have "relative" classes having much the same sematics and interface but different data organization as the "primary" class. Especially the wide deployment of the lazy evaluation technique has born a plenty of relative classes. For instance, an object representing a sum of two vectors or a slice of a vector (both using lazy evaluation) should be usable in every context where a normal vector object (or at least an immutable one) is accepted.

The classical approach here would be to define an abstract vector base class with all methods declared as pure virtual functions, and derive everything from it. Although it would be a clearer design, it causes a serious performance penalty due to the fact that virtual functions practically inhibit inlining, and thus impair the compiler's optimization. Fortunately, a template library can make use of more tricky concepts.

A generic class is a template analogon for a classical abstract class. PTL defines a generic class template for each family of related classes, for instance, GenericVector for everything looking like a vector. As in the abstract class approach, all concrete classes have to be derived from the generic class. And exactly as an abstract class, a generic class should not be instantiated "naked"; to enforce this, its constructor and destructor are always defined protected.

To eliminate the need for virtual functions, an instance of the generic class has to know statically (i.e., already during the compilation,) what concrete class it belongs to. This is achieved by parameterizing the generic class (you remember, it's a template!) with the concrete class derived from it. Staying by the vectors, an example would look like this:

   template <typename _Vector>
   class GenericVector {
      typedef _Vector top_type;
      ...
   };
   template <typename ElementType>
   class Vector : public GenericVector< Vector<ElementType> > { ... };

It this setting it is very easy for the generic class instance to achieve the full concrete object: it's just a { static_cast<top_type*>(this) }. Such an inverted cast is explicitly allowed by the ANSI/ISO Standard C++. To keep the code more readable, each generic class defines a top() method encapsulating exactly this cast (in fact, two methods, with and without const.)

The same cast can be applied by every external function working with class families as well:

   template <typename _Vector>
   void f(const GenericVector<_Vector>& v) {
      int s=v.top().size();
      ...
   }

In many cases, however, the cast is not necessary, since many methods are defined already in the generic class; please consult the documentation of the corresponding class family.

The generic classes can also carry additional attributes, allowing for more fine-granular distinction between overloaded template functions. All these attributes are declared with default settings extracted from the concrete class, therefore the application functions don't always need to refer to them explicitly. The real PTL definition of the GenericVector class has the vector element type as the second parameter:

   template <typename _Vector, typename ElementType=typename _Vector::element_type>
   class GenericVector {
      typedef ElementType element_type;
      ...
   };

Now you can easily distinct between two versions of an algorithm, one requiring two vectors of identical element type, and another handling the heterogeneous case:

   // homogeneous case
   template <typename _Vector1, typename _Vector2, typename ElementType>
   void f(const GenericVector<_Vector1, ElementType>& v1, const GenericVector<_Vector2, ElementType>& v2);
   // heterogeneous case
   template <typename _Vector1, typename _Vector2>
   void f(const GenericVector<_Vector1>& v1, const GenericVector<_Vector2>& v2);

Caveats

However smart this technique might be, it has its not negligible caveats. First of all, it is the problem of so called code bloat: each function designed to work with a generic family has to be a template in its turn. Often this "templatization" spreads itself over the entire program module, stopping only at the main() function. This leads to considerable code size growth and to an immense time and memory consumption by the compiler.

Another problem arises when you make a mistake in your template code: the compiler diagnostics are very heavy to understand, most of them referring to the code you had not written yourself, namely the guts of the template library. Mistakes also often stay undiscovered for long periods of time, until the method containing it is really used (due to the lazy template instantiation deployed by the most of the modern C++ compilers.) There are some recently invented approaches that can alleviated this, in particular the concept checks originating from the Boost library. They will be probably built in PTL, as soon as their deployment in STL will seem stable enough.