twm's homepage logo
From someone who traveled abroad to learn appreciating his own country ...
Object Pascal (Delphi) Templates
Deutsch English
Google
Search dummzeuch.de
Search WWW

Introduction

You probably know this situation: A C++ enthusiast is trying to "convince" you that C++ is better than Object Pascal, because OP is lacking some "important features of a modern object oriented programming language". If you ask him which he will usually give you examples of things that are actually supported in OP and have been for years. At that point he will then either start talking about that "everybody knows" that C++ compilers generate better optimized code than compilers for other programming languages and therefore you must use C++ for everything (which is bollocks as any decent performance comparison will prove). Or he will think up some rather obscure feature of C++.

One of these obscure features, which less than 50% of all C++ programmers ever use, are templates or generics. And Delphi doesn't support them, or does it?

What is a template?

OK, let's assume we are stupid and ask "What is a template?" (In the German original this is an adapted quote from "Die Feuerzangenbowle" a very good and funny movie with Heinz Ruehmann.

A template - or generic - is an abstraction of an algorithm from the data it works on. In C++ templates are part of the language. If you ever ran into something like the following, you have actually seen a template in the wild:

                      
//--- file CheckedArray.h
#ifndef CHECKEDARRAY_H
#define CHECKEDARRAY_H
#include <stdexcept>
/////////////////////////////////// class CheckedArray<T>
template<class T>
class CheckedArray {
private:
    int size;  // maximum size
    T* a;      // pointer to new space
public:
    //================================= constructor
    CheckedArray<T>(int max) {
        size = max;
        a = new T[size];
    }//end constructor
    //================================= operator[]
    T& CheckedArray<T>::operator[](int index) {
        if (index < 0 || index >= size)
            throw out_of_range("CheckedArray");
        }
        return a[index];
    }//end CheckedArray<T>
};//end class CheckedArray<T>
#endif
                    
                    
//--- file test.cpp
#include "CheckedArray.h"
//================================= main test program
void main() {
    CheckedArray<double> test1(100);
    test1[25] = 3.14;
    CheckedArray<int> test2(200);
    test2[0] = 55;
}//end main
                  

(from: http://leepoint.net/notes/cpp/oop-templates/template-ex1.html)

This is a class with an array which does a bounds check on the index on every access. (Yes, in C++ this not a built in compiler feature, you must implement it yourself.)

The syntax

                  
template<class T>
class CheckedArray {
}
                

means that it is a template, where T is a class that can be parameterized. The actual class is provided when the template is used like this:

                
CheckedArray<double> test1(100);
              

This means that the template is to be used with T = double.

Templates are converted by the C++ compiler (actually it is done by the preprocessor, but who cares about this difference?) into copies of the template code. This means that with each usage of a template the whole code for this template will be added to the program again. This could explain why C++ programs are that large. ;-) But this and other similarly complex processes are definitely the reason why C++ compilers are that slow (Typical compile times for a few ten thousand lines of code in Delphi: 5-10 seconds, in C++ several minutes).

There is the so called Standard Template Library (STL) which comes with every decent C++ compiler (The one from Microsoft came with some serious bugs but there is an inofficial patch available for it). It contains mostly templates for different container classes. Those containers are supposed to be optimized for performance or memory usage respectively so that in theory the programmer only needs to select the correct container for his problem to write efficient code.

Templates in Object Pascal

First I must disappoint you: There is no secret compiler switch which enables templates in Delphi (or Kylix, for that matter, because what I describe here works with "Delphi for Linux" too).

But that isn't necessary, because Delphi already supports everything we need for this, only the syntax is a bit cumbersome (otoh the example above wasn't easy to read either, was it?). All it needs are a few conditional defines and include files. (If you are as old as I, you probably remember that even the good old Turbo Pascal had these features, it's just that nobody had the idea to use them in this way.)

Here is a simple template in Object Pascal:

                
{$IFNDEF TYPED_OBJECT_LIST_TEMPLATE}
unit t_TypedObjectList;

interface

uses
  Sysutils,
  Classes,
  Contnrs;

type
  _TYPED_OBJECT_LIST_ITEM_ = TObject;
{$ENDIF TYPED_OBJECT_LIST_TEMPLATE}

{$IFNDEF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}
type
  _TYPED_OBJECT_LIST_ = class
  protected
    fList: TObjectList;
    function GetItems(_Idx: integer): _TYPED_OBJECT_LIST_ITEM_;
  public
    constructor Create;
    destructor Destroy; override;
    function Add(_Item: _TYPED_OBJECT_LIST_ITEM_): integer;
    property Items[_Idx: integer]: _TYPED_OBJECT_LIST_ITEM_
      read GetItems; default;
  end;

{$ENDIF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}

{$IFNDEF TYPED_OBJECT_LIST_TEMPLATE}
implementation
{$DEFINE TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}
{$ENDIF TYPED_OBJECT_LIST_TEMPLATE}

{$IFDEF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}

{ _TYPED_OBJECT_LIST_ }

constructor _TYPED_OBJECT_LIST_.Create;
begin
  inherited Create;
  fList := TObjectList.Create;
end;

destructor _TYPED_OBJECT_LIST_.Destroy;
begin
  fList.Free;
  inherited;
end;

function _TYPED_OBJECT_LIST_.Add(
  _Item: _TYPED_OBJECT_LIST_ITEM_): integer;
begin
  Result := fList.Add(_Item);
end;

function _TYPED_OBJECT_LIST_.GetItems(
  _Idx: integer): _TYPED_OBJECT_LIST_ITEM_;
begin
  Result := fList[_Idx] as _TYPED_OBJECT_LIST_ITEM_;
end;

{$WARNINGS off}
{$IFNDEF TYPED_OBJECT_LIST_TEMPLATE}
end.
{$ENDIF TYPED_OBJECT_LIST_TEMPLATE}
{$ENDIF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}
{$DEFINE TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}
              

If you ignore the IFDEFs, DEFINEs etc. for the moment this is an ordinary class declaration in an ordinary unit. We declare a class __TYPED_OBJECT_LIST__ which internally uses a TObjectList to store objects of type __TYPED_OBJECT_LIST_ITEM__. To make it easier to understand, I have omitted all but the essential methods and properties (Add and Items). The complete unit is available for download.

OK, now how do I use this thing? It is even simpler:

            
unit u_MemoList;

interface

uses
  Sysutils,
  Classes,
  Contnrs,
  StdCtrls;

{$define TYPED_OBJECT_LIST_TEMPLATE}
type
  _TYPED_OBJECT_LIST_ITEM_ = TMemo;
{$INCLUDE 't_TypedObjectList.tpl'}

type
  TMemoList = class(_TYPED_OBJECT_LIST_)
  end;

implementation

{$INCLUDE 't_TypedObjectList.tpl'}

end.
          

This example declares a container TMemoList to type safely store TMemo objects.

You can simply use this unit in a program, create an instance of TMemoList and store memos in it. Accessing those memos can be done conveniently without any type conversion:

          
  SomeMemoList[0].Lines.Text := 'new memo text';
        

How does it work?

As said before, there is no magic here. We use only features of the Delphi compiler that it has had for years even in its early incarnations as Turbo Pascal.

The trick is to include the unit twice as an include file and by using conditional defines and IF(N)DEFs exclude the parts that aren't wanted.

A {$IFNDEF TYPED_OBJECT_LIST_TEMPLATE} ... {$ENDIF} brackets all those parts the compiler should never see.

A {$IFNDEF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS} ... {$ENDIF} brackets the part that should only be available in the first pass (interface) and a {$IFDEF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS} ... {$ENDIF} brackets the part that should only be available in the second pass (implementation).

It would be possible to omit those defines by having two separate include files, one for the interface and one for the implementation. But this has got two drawbacks:

  1. Splitting the template into two files is not just unelegant but also error prone.
  2. The template unit can during development of the template not be used as an ordinary unit, giving away the priceless help of the compiler.

Details

Let's have a look at the template unit again, this time split into easily palatable parts:

        
{$IFNDEF TYPED_OBJECT_LIST_TEMPLATE}
unit t_TypedObjectList;

interface

uses
  Sysutils,
  Classes,
  Contnrs;

type
  _TYPED_OBJECT_LIST_ITEM_ = TObject;
{$ENDIF TYPED_OBJECT_LIST_TEMPLATE}
      

Since we don't realy use it as a unit the unit header including the uses clause is commented out via IFNDEF. During develompent of the template you can compile the unit like any ordinary unit to simplify finding any bugs. But when later used as a template you define the conditional define TYPED_OBJECT_LIST_TEMPLATE and so make the compiler ignore the unit header. Also we want to ignore the declaration of _TYPED_OBJECT_LIST_ITEM_, because we want to parameterize this type.

Now let's look at the type declaration of the container _TYPED_OBJECT_LIST_:

      
{$IFNDEF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}
type
  _TYPED_OBJECT_LIST_ = class
  protected
    fList: TObjectList;
    function GetItems(_Idx: integer): _TYPED_OBJECT_LIST_ITEM_;
  public
    constructor Create;
    destructor Destroy; override;
    function Add(_Item: _TYPED_OBJECT_LIST_ITEM_): integer;
    property Items[_Idx: integer]: _TYPED_OBJECT_LIST_ITEM_
      read GetItems; default;
  end;

{$ENDIF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}
    

Here we have got another conditional define TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS. This one makes the compiler read this part only the first time it is included. The rest is an ordinary class declaration like those you have seen a thousand times. Only the type names probably look a bit strange to you. You can choose them freely, actually, the same goes for the names of the conditional defines. I have chosen this special form to emphasize that they are no ordinary types.

    
{$IFNDEF TYPED_OBJECT_LIST_TEMPLATE}
implementation
{$DEFINE TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}
{$ENDIF TYPED_OBJECT_LIST_TEMPLATE}
  

Again a conditional define lets the compiler ignore this part if it is used as a template. This also means that it will ignore the define for TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS, because this is only used during the development of the template.

The following implementation section is, again, rather boring:

  
{$IFDEF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}

{ _TYPED_OBJECT_LIST_ }

constructor _TYPED_OBJECT_LIST_.Create;
begin
  inherited Create;
  fList := TObjectList.Create;
end;

destructor _TYPED_OBJECT_LIST_.Destroy;
begin
  fList.Free;
  inherited;
end;

function _TYPED_OBJECT_LIST_.Add(
  _Item: _TYPED_OBJECT_LIST_ITEM_): integer;
begin
  Result := fList.Add(_Item);
end;

function _TYPED_OBJECT_LIST_.GetItems(
  _Idx: integer): _TYPED_OBJECT_LIST_ITEM_;
begin
  Result := fList[_Idx] as _TYPED_OBJECT_LIST_ITEM_;
end;

{$WARNINGS off}
{$IFNDEF TYPED_OBJECT_LIST_TEMPLATE}
end.
{$ENDIF TYPED_OBJECT_LIST_TEMPLATE}
{$ENDIF TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}

Again we have got a bracketing IFDEF .. ENDIF with the conditional define TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS already mentioned above. This makes the compiler ignore this part in the first pass. It will only see the line

  
{$DEFINE TYPED_OBJECT_LIST_TEMPLATE_SECOND_PASS}

following the ENDIF. This defines this symbol for the second pass.

What the compiler actually sees.

With all these INCLUDEs and IFDEFs we make the compiler see a totally different source than what we see. The following is what the compiler actually compiles. It starts with the unit u_MemoList, includes the template unit t_TypedObjectList twice and of course omits what the compiler cannot see.

I hope this will help all those, who are not yet sure they understand, to see the light. ;-)

  
unit u_MemoList;

interface

uses
  Sysutils,
  Classes,
  Contnrs,
  StdCtrls;

type
  _TYPED_OBJECT_LIST_ITEM_ = TMemo;

type
  _TYPED_OBJECT_LIST_ = class
  protected
    fList: TObjectList;
    function GetItems(_Idx: integer): _TYPED_OBJECT_LIST_ITEM_;
  public
    constructor Create;
    destructor Destroy; override;
    function Add(_Item: _TYPED_OBJECT_LIST_ITEM_): integer;
    property Items[_Idx: integer]: _TYPED_OBJECT_LIST_ITEM_
      read GetItems; default;
  end;

type
  TMemoList = class(_TYPED_OBJECT_LIST_)
  end;

implementation

{ _TYPED_OBJECT_LIST_ }

constructor _TYPED_OBJECT_LIST_.Create;
begin
  inherited Create;
  fList := TObjectList.Create;
end;

destructor _TYPED_OBJECT_LIST_.Destroy;
begin
  fList.Free;
  inherited;
end;

function _TYPED_OBJECT_LIST_.Add(
  _Item: _TYPED_OBJECT_LIST_ITEM_): integer;
begin
  Result := fList.Add(_Item);
end;

function _TYPED_OBJECT_LIST_.GetItems(
  _Idx: integer): _TYPED_OBJECT_LIST_ITEM_;
begin
  Result := fList[_Idx] as _TYPED_OBJECT_LIST_ITEM_;
end;

end.

Convinced? Easy, isn't it? At least for the compiler. ;-)

Conclusion

Why doing this?

Yes, that's a good question. It took me quite a while before I started using templates like this. I was so used to TList and friends and typecasts (and the 'as' operator) that I didn't realize how error prone that can be. After seeing the light ;-) my templates have started multiplying like rabbits, especially those that implement several different sorts of containers ((sorted) collections, vectors, queues, stacks etc.)

The nice thing about it is, that you need to do do the hard bit - writing the code - only once, using it is simple enough so that even the non-wizard software developers can master it easily. All they need to do is adding two blocks of code into their units. In the interface section:

  
{$define TYPED_OBJECT_LIST_TEMPLATE}
type
  _TYPED_OBJECT_LIST_ITEM_ = <type to save>;
{$INCLUDE 't_TypedObjectList.tpl'}

and in the implementation section:

  
{$INCLUDE 't_TypedObjectList.tpl'}

Those blocks can for example be saved as Code Insight code templates. The template user doesn't need to understand the tricks that are used internally. He can even use the debugger to single step through the code.

This is not just a pointless proof for the C++ enthusiasts that it is possible, it is also useful in real life programming. Given time I hope to create a template library resembling to an extend the STL of the C++ world.

Why not use a code generator?

Most of you will be familiar with some sort of code genrator. There are some which will generate typed lists as a descendant class of TList. I don't like those, because they will extend TList with additional methods and properties but the original ones will still be there. This violates the object oriented paradigma of hiding all information that is not necessary.

My first approach was to write my own code generator which uses a template source code with wildcards so it is very flexible and can generate a wide variety of different structures.

The the main disadvantage of it was that I was duplicating the code rather than reusing it. If there was a bug in my template I had to either fix it in all the sources generated from it or regenerate the sources again from the fixed template. Both approaches were too error prone.

With the pseudo templates introduced here this problem does not exist. Just rebuilding the executable will automatically include the fix for all implementations of a template.

Credits

I would never have come up with this idea on my own. One day I came across a posting from Radek Jedrasiak in borland.public.delphi.objectpascal where he wrote about C++ like templates in Object Pascal for which he had made a small improvement. I found the principle quite interesting and read the original article by Rossen Assenov (http://community.borland.com/article/0,1410,27603,00.html registration mandatory) and the comments there. The idea was born.

(c) Copyright 2002-2003 by Thomas Mueller, all rights reserved



This document was generated using AFT v5.095

last updated: 2012-10-14 twm
Post to del.icio.us Best Viewed With Open EyesValid XHTML 1.0!