Last Updated:

Templates in Object Pascal

Probably every Delphi programmer at least once communicated with a C++ programmer and explained how Delphi is more powerful and convenient. But at some point, a C++ programmer states something like this: "OK, but Delphi uses Pascal, which means it doesn't support multiple inheritance and patterns, so it's not as good as C++."

As for multiple inheritance, you can easily say that Delphi has interfaces that perfectly cope with their task, but you will have to agree about templates, since Object Pascal does not support them.

Let's take a closer look at this problem.

Templates allow you to make generic containers such as lists, stacks, queues, etc. If you want to do something similar in Delphi, then you have two ways:

  1. Use a TList container that contains pointers. In this case, you will have to do explicit type conversion all the time.
  2. Subclass the container TCollection or TObjectList, and remove all type-specific methods each time you want to use a new data type.

The third option is to make a module with a generic container class, and every time we need to use a new data type, we will have to look for and make corrections in the editor. It would be great if the compiler did all this work for you.... that's what we're going to do now!

For example, take the TCollection and TCollectionItem classes. When you declare a new child of the TCollectionItem, you also inherit the new class from TOwnedCollection and override most of the methods so that they can be called with the new types.

Let's see how to create a generic collection of class templates:

Step 1: Create a new text file (non-Unit) named TemplateCollectionInterface.pas:

_COLLECTION_ = class (TOwnedCollection)
protected
 function  GetItem (const aIndex : Integer) : _COLLECTION_ITEM_;
 procedure SetItem (const aIndex : Integer;
                    const aValue : _COLLECTION_ITEM_);
public
 constructor Create (const aOwner : TComponent);

 function Add                                 : _COLLECTION_ITEM_;
 function FindItemID (const aID    : Integer) : _COLLECTION_ITEM_;
 function Insert     (const aIndex : Integer) : _COLLECTION_ITEM_;
 property Items      [const aIndex : Integer] : _COLLECTION_ITEM_
                                         read GetItem write SetItem;
end;

Note that there are no uses or interface clauses, only a generic type declaration, in which _COLLECTION_ is the name of the generic collection of the class, and _COLLECTION_ITEM_ is the name of the methods contained in our template.

Step 2: Create a second text file and save it as TemplateCollectionImplementation.pas:

constructor _COLLECTION_.Create (const aOwner : TComponent);
begin
 inherited Create (aOwner, _COLLECTION_ITEM_);
end;

function _COLLECTION_.Add : _COLLECTION_ITEM_;
begin
 Result := _COLLECTION_ITEM_ (inherited Add);
end;

function _COLLECTION_.FindItemID (const aID : Integer) : _COLLECTION_ITEM_;
begin
 Result := _COLLECTION_ITEM_ (inherited FindItemID (aID));
end;

function _COLLECTION_.GetItem (const aIndex : Integer) : _COLLECTION_ITEM_;
begin
 Result := _COLLECTION_ITEM_ (inherited GetItem (aIndex));
end;

function _COLLECTION_.Insert (const aIndex : Integer) : _COLLECTION_ITEM_;
begin
 Result := _COLLECTION_ITEM_ (inherited Insert (aIndex));
end;

procedure _COLLECTION_.SetItem (const aIndex : Integer;
                                const aValue : _COLLECTION_ITEM_);
begin
 inherited SetItem (aIndex, aValue);
end;

Again, there are no uses or interface clauses, only generic type code.

Step 3: Create a new unit file named MyCollectionUnit.pas:

unit MyCollectionUnit;

interface

uses Classes;

type TMyCollectionItem = class(TCollectionItem)
     private
      FMyStringData : String;
      FMyIntegerData : Integer;
     public
      procedure Assign(aSource : TPersistent); override;
     published
      property MyStringData : String read FMyStringData write FMyStringData;
      property MyIntegerData : Integer read FMyIntegerData write FMyIntegerData;
     end;

     // !!! Pointing the generic class to a real type
     
     _COLLECTION_ITEM_ = TMyCollectionItem;
     
     // !!! generic class interface add directive

     {$INCLUDE TemplateCollectionInterface}

     // !!! rename generic class

     TMyCollection = _COLLECTION_;

implementation

uses SysUtils;

// !!! preprocessor directive to add a generic class

{$INCLUDE TemplateCollectionImplementation}

procedure TMyCollectionItem.Assign(aSource : TPersistent);
begin
 if aSource is TMyCollectionItem then
 begin
  FMyStringData := TMyCollectionItem(aSource).FMyStringData;
  FMyIntegerData := TMyCollectionItem(aSource).FMyIntegerData;
 end
 else inherited;
end;

end.

That's it! Now the compiler will do all the work for you! If you change the generic class interface, the changes will automatically propagate to all modules it uses.

Second example

Let's create a generic class for dynamic arrays.

Step 1: Create a text file named TemplateVectorInterface.pas:

_VECTOR_INTERFACE_ = nterface
 function  GetLength : Integer;
 procedure SetLength (const aLength : Integer);

 function  GetItems (const aIndex : Integer) : _VECTOR_DATA_TYPE_;
 procedure SetItems (const aIndex : Integer;
                     const aValue : _VECTOR_DATA_TYPE_);

 function  GetFirst : _VECTOR_DATA_TYPE_;
 procedure SetFirst (const aValue : _VECTOR_DATA_TYPE_);

 function  GetLast  : _VECTOR_DATA_TYPE_;
 procedure SetLast  (const aValue : _VECTOR_DATA_TYPE_);

 function  High  : Integer;
 function  Low   : Integer;

 function  Clear                              : _VECTOR_INTERFACE_;
 function  Extend   (const aDelta : Word = 1) : _VECTOR_INTERFACE_;
 function  Contract (const aDelta : Word = 1) : _VECTOR_INTERFACE_; 

 property  Length     : Integer             read GetLength write SetLength;
 property  Items 
        [const aIndex : Integer] :
                        _VECTOR_DATA_TYPE_  read GetItems  write SetItems; default;
 property  First      : _VECTOR_DATA_TYPE_  read GetFirst  write SetFirst;
 property  Last       : _VECTOR_DATA_TYPE_  read GetLast   write SetLast;
end;

_VECTOR_CLASS_ = class (TInterfacedObject, _VECTOR_INTERFACE_)
private
 FArray : array of _VECTOR_DATA_TYPE_;
protected
 function  GetLength : Integer;
 procedure SetLength (const aLength : Integer);

 function  GetItems (const aIndex : Integer) : _VECTOR_DATA_TYPE_;
 procedure SetItems (const aIndex : Integer;
                     const aValue : _VECTOR_DATA_TYPE_);

 function  GetFirst : _VECTOR_DATA_TYPE_;
 procedure SetFirst (const aValue : _VECTOR_DATA_TYPE_);

 function  GetLast  : _VECTOR_DATA_TYPE_;
 procedure SetLast  (const aValue : _VECTOR_DATA_TYPE_);
public
 function  High  : Integer;
 function  Low   : Integer;

 function  Clear                              : _VECTOR_INTERFACE_;
 function  Extend   (const aDelta : Word = 1) : _VECTOR_INTERFACE_;
 function  Contract (const aDelta : Word = 1) : _VECTOR_INTERFACE_; 

 constructor Create (const aLength : Integer);
end;

Step 2: Create a text file and save it as TemplateVectorImplementation.pas:

constructor _VECTOR_CLASS_.Create (const aLength : Integer);
begin
 inherited Create;

 SetLength (aLength);
end;

function _VECTOR_CLASS_.GetLength : Integer;
begin
 Result := System.Length (FArray);
end;

procedure _VECTOR_CLASS_.SetLength (const aLength : Integer);
begin
 System.SetLength (FArray, aLength);
end;

function _VECTOR_CLASS_.GetItems (const aIndex : Integer) : _VECTOR_DATA_TYPE_;
begin
 Result := FArray [aIndex];
end;

procedure _VECTOR_CLASS_.SetItems (const aIndex : Integer;
                                   const aValue : _VECTOR_DATA_TYPE_);
begin
 FArray [aIndex] := aValue;
end;

function _VECTOR_CLASS_.High : Integer;
begin
 Result := System.High (FArray);
end;

function _VECTOR_CLASS_.Low : Integer;
begin
 Result := System.Low (FArray);
end;

function _VECTOR_CLASS_.GetFirst : _VECTOR_DATA_TYPE_;
begin
 Result := FArray [System.Low (FArray)];
end;

procedure _VECTOR_CLASS_.SetFirst (const aValue : _VECTOR_DATA_TYPE_);
begin
 FArray [System.Low (FArray)] := aValue;
end;

function _VECTOR_CLASS_.GetLast : _VECTOR_DATA_TYPE_;
begin
 Result := FArray [System.High (FArray)];
end;

procedure _VECTOR_CLASS_.SetLast (const aValue : _VECTOR_DATA_TYPE_);
begin
 FArray [System.High (FArray)] := aValue;
end;

function _VECTOR_CLASS_.Clear : _VECTOR_INTERFACE_;
begin
 FArray := Nil;

 Result := Self;
end;

function _VECTOR_CLASS_.Extend (const aDelta : Word) : _VECTOR_INTERFACE_;
begin
 System.SetLength (FArray, System.Length (FArray) + aDelta);

 Result := Self;
end;

function _VECTOR_CLASS_.Contract (const aDelta : Word) : _VECTOR_INTERFACE_;
begin
 System.SetLength (FArray, System.Length (FArray) - aDelta);

 Result := Self;
end;

Step 3: Create a unit file named FloatVectorUnit.pas:

unit FloatVectorUnit;

interface

// !!! The "Classes" module contains the declaration of the TInterfacedObject class
uses Classes;

// !!! data type for array class Double
type _VECTOR_DATA_TYPE_ = Double;

      {$INCLUDE TemplateVectorInterface}

// !!! give the interface a meanigful name
      IFloatVector = _VECTOR_INTERFACE_;
// !!! give the class a meanigful name
      TFloatVector = _VECTOR_CLASS_;

// !!! additional function
function CreateFloatVector (const aLength : Integer = 0) : IFloatVector;

implementation

{$INCLUDE TemplateVectorImplementation}

function CreateFloatVector (const aLength : Integer = 0) : IFloatVector;
begin
  Result := TFloatVector.Create(aLength);
end;

end.

Naturally, it is possible to supplement a universal class with additional functions. It all depends on your imagination!

Using templates

Here's an example of using the new vector interface:

procedure TestFloatVector;
 var aFloatVector : IFloatVector;
     aIndex       : Integer;
begin
 aFloatVector := CreateFloatVector;

 aFloatVector.Extend.Last := 1;
 aFloatVector.Extend.Last := 2;

 for aIndex := aFloatVector.Low to aFloatVector.High do
 begin
  WriteLn (FloatToStr (aFloatVector [aIndex]));
 end;
end.

The only requirement when creating templates in this way is that each new type must be declared in a separate module, and you must have sources for generic classes.