Skip to content

D2009 Generics and Type Constraints

The .NET type system is rooted at System.Object. The Delphi/Win32 type system isn’t rooted (built-in simple types, records, and classes don’t have a common ancestor), and primitive types can’t/don’t implement interfaces, so someone in the newsgroups asked how to deal with generics which must perform operations on primitive types, without the use of IComparable, et. al. Here’s one way. I’m not sure if it’s the best way, but it works.

unit Unit5;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TAdder<T> = class
  public
    function AddEm(A, B: T): T; virtual; abstract;
  end;

  TIntAdder = class(TAdder<integer>)
  public
    function AddEm(A, B: integer): integer; override;
  end;

  TStringAdder = class(TAdder<string>)
  public
    function AddEm(A, B: string): string; override;
  end;

  TMath<T; A: TAdder<T>, constructor> = class
  public
     function AddTwoDigits(Left, Right: T): T;
  end;

  TForm5 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

var
  Form5: TForm5;

implementation

{$R *.dfm}

procedure TForm5.Button1Click(Sender: TObject);
var
  AddInt: TMath<integer, TIntAdder>;
  AddString: TMath<string, TStringAdder>;
begin
  AddInt := TMath<integer, TIntAdder>.Create;
  try
    ShowMessage(IntToStr(AddInt.AddTwoDigits(2, 2)));
  finally
    AddInt.Free;
  end;
  AddString := TMath<string, TStringAdder>.Create;
  try
    ShowMessage(AddString.AddTwoDigits('2', '2'));
  finally
    AddString.Free;
  end;
end;

{ TIntAdder }

function TIntAdder.AddEm(A, B: integer): integer;
begin
  Result := A + B;
end;

{ TStringAdder }

function TStringAdder.AddEm(A, B: string): string;
begin
  Result := IntToStr(StrToInt(A) + StrToInt(B));
end;

{ TMath<T, A> }

function TMath<T, A>.AddTwoDigits(Left, Right: T): T;
var
  Add: A;
begin
  Add := A.Create;
  try
    Result := Add.AddEm(Left, Right);
  finally
    Add.Free;
  end;
end;

end.

{ 18 } Comments

  1. Daniel Lehmann | August 29, 2008 at 10:07 am | Permalink

    So in order to solve a problem that comes up with a valid use-case for generics, we have to go back to copy&paste programming…nice ;)

    I am really starting to love Scala (http://www.scala-lang.org/) which has beautiful solutions to those problems…

  2. Daniel Lehmann | August 29, 2008 at 10:13 am | Permalink

    Oh an btw…could you please try out whether records can be generic, too?

    type
    TMyAdder = record
    public function Add(V1, V2: T): T;
    end;

    Something like that… :)

  3. Daniel Lehmann | August 29, 2008 at 10:29 am | Permalink

    Yes of course its technically different. As Delphi was not originally designed with those things in mind it is even understandable.

    But we have to derive a new class for each value type that we want to use (even TDateTime and possible own TComplex or whatever). And that is exactly the problem that generics were to solve?

    All I am saying it is a little sad that the Delphi implementation seems to be even more limited than the .Net one which is already limited. I once wrote a generic for a family of classes which all had to be specialized. It turned out to be a complete mess because the language (C#) was just too limited. In the end, a solution using "object"s everywhere might have been better.

    But of course for simple things like TLists, TCollection etc those collections are already a great help.

  4. Daniel Lehmann | August 29, 2008 at 10:36 am | Permalink

    Sorry for not sh*tting up ;)

    Wouldn’t it be possible to make TAdder .AddEm a non-static class function? In that case you could get rid of the constructor requirement and never have to create the class….

  5. Sergey Antonov | August 29, 2008 at 1:23 pm | Permalink

    HI, Craig Stuntz.

    After spending some time(about 4 hours) with new Delphi 2009, I have deduced that generics implementation is not actuality generics.
    It is a very restricted implementation of generics.

    1. Restriction of impicity or explicity types coercion of typed parameter.
    2. Restriction of use operator IS And AS.

    I of course understand this restriction on native side of Delphi. Becase there is no deterministic way convert T(typed parameter) to boxed type(managed type).
    On IL side there is an "box " opcode.

    3. Why R&D of Codegear(compiler guys) generate generics instantiation x86 code, but restrict using the binary and unary operators.

    I know that there is no unityped IL
    (CEQ instruction of IL is valid for INT, F, O, & types) opcode for compare OP in .NET.
    Why this rescriction is carried to native implementation.

    I of course understand the way to solve this
    IComparer.

    But suppose that collection(Tlist) is a big collection.
    And I try to find out an element with indexOf method of Tlist.

    Penalty of using gelegated implementation of compare operation(dispatch via VMT of interface) is very essential.
    Why not use the inlined code in place where compare operation occurs.

    And the next restrion of IComparer.is that an operands have to be of the same type(T)

    And yours samples just confirm the problem of generics implementation of native Delphi.

    The goal of using TMath is just that instation, remove operations are generalized.

    But for every type you must provide an impementator of operation.

    And you must provide valid pairs of typed parameters to TMath.
    So you deterministicaly define pairs. So there is no generalization at all.

    And you of course may translate yourscode (Add operation) to IComparer implementation sample in Generics.Defaults unit.
    and reduce TMath> to TMath. But I see this implemenation and think that such impementation will be difficult for many Delphi progrmamers(>90% of delphi community).
    But in any case that dispatch would be via VMT.

    I think that Combination of .Net generics and C++ templates would be the best implemenation for class parameterization.

    Sorry for my English. Sergey Antonov from Russia.

  6. Sergey Antonov | August 29, 2008 at 2:25 pm | Permalink

    Craig Stuntz,
    I just have said that CG guys copy C# (read .NET features) and it is not just my opinion. It is very sadly. I am not C# programmer, I’m Delphi man.
    So, direct copy(with resctiction of type coercion) of generics implemenatation form C# with an unability of direct using operators on T is bad solution. IMHO.

    Additionaly to my first post.

    If you want to add values from different types or compare different types the IComparer will fails.
    Why doesn’t to use at compile time overloads versions of appropriate operators in classes.
    So for generics you would duplicate an implementation for overloads operators with wrapped class.
    A one for any different combination of types.

    And dont forget the penalty for indirect call via VMT on big collections.

  7. Raymond | August 29, 2008 at 4:24 pm | Permalink

    Putting all the arguments aside regarding the purity, or not, of Delphi generics when compared to C#, I think I would be disturbed to find code like this dealing with primitive types (with or without generics)

    I think the overhead of supporting primitive type operations like this in generics would be appalling, and really only useful for relatively trivial/simple/small problems and datasets (or ‘educational’/'interesting’ examples of how to use generics). What’s worse, the syntax required to implement this very simple operation is pretty icky, and definitely not conducive to devs understanding what’s going on. So much for code maintainability :-(

  8. Sergey Antonov | August 31, 2008 at 6:13 am | Permalink

    Craig, I can’t realize how operator constraints may
    assist us in gaining an ability to use operators with typed parameters.
    The idea of constraints is to reduce superset of types to subset with identical behaviour, so any actions with this subset would be valid at compile time and it results in verified IL code.

    The restrictions of using operators in generics in .NET is that at instantiation time(run time) there is no way to guarantee that method operators for any combination of types as parameters exists.
    Of course is not impracticable task, but it
    complicates implementation of .NET.
    But this code would not be granted as safe code.

    And additional disadvantage is that no more shared x86 code for different instantiations of same generic class with managed type typed parameters only(not value type typed parameters).

    But we live in native world of Delphi, and all code(99,9999999%) is generated at compile time, except some hack code stubs in jungle of VCL.

    So generated code will be always safe in manner of applying valid operations to types in parametrized class instantiations, if this code passes compile time.

    Are there any ways to get to know of native Delphi language enchancement in next releases, except x64 and DPL?

    Thank you.

  9. David M | August 31, 2008 at 9:03 pm | Permalink

    Hi Craig,

    In the line:
    TMath, constructor> = class
    can you tell me what the constructor keyword means, please? I can’t see a constructor method declared there or implemented for TMath later. Does it in effect specify T must be a type with a constructor, ie an object?

    I just haven’t seen this notation before in articles I’ve read about Delphi’s generics.

    Cheers,

    David

  10. David M | August 31, 2008 at 9:05 pm | Permalink

    Sorry, looks like the form at some of the symbols in the line I was quoting. It was the first line of the class declaration for TMath.

    Cheers,

    David

  11. Raymond | August 31, 2008 at 11:41 pm | Permalink

    I appreciate it is a workaround. I suppose my message here is given this limitation, generics probably shouldn’t be going near primitive types (if only for the performance issue)!

  12. Sergey Antonov | September 1, 2008 at 12:29 pm | Permalink

    Craig, here is the bug reproduction in generics implementation on Win32

    Try this

    TGenericRecord = record
    procedure DoSomething(aParam: T);
    end;

    procedure TForm1.FormCreate(Sender: TObject);
    var a:TGenericRecord;
    b:integer;
    begin
    a.DoSomething(5);
    end;

    procedure TGenericRecord.DoSomething(aParam: T);
    var a:integer;
    begin
    a:=integer((@aParam)^);

  13. Sergey Antonov | September 2, 2008 at 12:21 pm | Permalink

    Good evening, Craig!

    First of all about bug,

    procedure Some"T".DoSomething(aParam: T);
    var a:integer;
    begin
    a:=integer((@aParam)^); // Bug here, hello to the EDX register
    ….
    end;

    Asm code equavalent of assign op

    Inner SEH FRAME

    push $0046c650
    push dword ptr fs:[eax]
    mov fs:[eax],esp

    a:=integer((@aParam)^); // Bug here, hello to the EDX register

    Problem instruction - Assign OP

    After this one we catch an Exception.

    lea eax,quiet_dl

    This instruction isn’t executed.
    mov ebx,[eax]

    Second.

    Today I was trying to improve my implementation of C# Yield operator implementation in native Delphi.
    You can find my free time research on santonov.blogspot.com.

    But I encountered a problem that is an unabitility to use any asm chunks in parameterized classes.

    Third. Recall to operators in parameterized classes.

    You suggest an interestring solution.

    Implicit(T):integer;

    Or may be even Implicit(T):U.

    And what about +,-,*,/ …. with different combination of all parameterized types.

    That about the expression?

    (U+T*Z-V*10*(cast to V) (U))/U

    Class implementer may to be at a loss by this expression.

    How many operator constraints has programmer to declare?

    Why would not this responsibility move to compiler?

    It is a power of native code.

    And constraints is necessity of .Net to generate safe code.

    But, in any case your idea is very good.

  14. Sergey Antonov | September 2, 2008 at 1:31 pm | Permalink

    Craig, thank you for you opinion. :)

    > In short, it’s a limitation of the compiler, and one >which isn’t likely to go away soon.

    The key words here is the limitation of the compiler. I think that the inner representation
    of parse tree is only the inner representation.

    Suppose the next code

    SomeClassA=class(Some)
    procedure abc;
    end;

    Why I can not write?

    procedure SomeClassA.abc;
    asm
    end;

    No dependency.
    Where and what is the barrier?
    What do you think about the relocable asm code representation in dcu?

    Or the next one

    procedure SomeClassA.abc;
    var a,b:T;
    begin

    a:=b;

    {relocable code.}
    asm
    lea eax,[a];
    end;

    b:=a;
    ..
    end;

    But!!! I think
    Problem is that generics in native Delphi is a satelite of generics in Delphi.NET.
    Resriction of using operators on T is just restriction of .NET.

  15. Sergey Antonov | September 2, 2008 at 2:06 pm | Permalink

    Craig, I try to answer your question.

    If your list of type constraints starts looking like a class definition, and I would question how "generic" your type really is?

    The class parametrization is just the way to define generic behaviour.

    So, the expression
    (U+T*Z-V*10*(cast to V) (U))/U is still generalized.

    But for code generation there must be a guarantee to accomplish compilation process successfully.

    For intermediate platforms, for generic IL code there is needs for additional info(constraints) to guarantee safe code.
    At compile time that analysis of course may be carried out by compiler on source code.
    So instantiation may valid. But at run time instantiation without constraints results in needs for reverse engineering of IL code to deduce constraints violation(an code of course).
    But instantiation of .NET generics occurs at run time so no constraints, no safe code.

    So for native platforms, there is no needs in any constraints, because all may be infered at compile time. But with one restriction there are no way to instantiate Generic class at run time with rational effort of course.

  16. Sergey Antonov | September 2, 2008 at 2:11 pm | Permalink

    Craing, I am very thankful to you.

    Thank you again.

  17. David M | September 8, 2008 at 12:39 am | Permalink

    "David, the constructor constraint specifies the signature of the constructor which the passed type argument must have. If memory serves, records can have constructors now, as well, so it’s not the same as a class constraint. In this case, I’m specifying a parameter less constructor."

    Thanks, Craig. I didn’t know that was possible. I can think of a number of ways that would be very useful.

    "OK, I see that I’m going to have to write some more posts on this subject in order to cover some of the questions and assertions in comments."

    I’d find another post interesting, and it would be great to have more information about some of this. Thanks for this one, though!

  18. Georges V | January 27, 2009 at 1:11 am | Permalink

    Adding yet another point on generics, in a cold post :

    for the record, some sort of template coding was already possible in previous versions of Delphi :
    -the "{$INCLUDE }" directive allows you to paste verbatim source code from some other file,
    -Delphi allows you to give any name to any existing type.

    So you can write :

    UNIT MappingIntegerString;

    INTERFACE

    TYPE
    _TEMPLATE_KEY_TYPE_ = Integer;
    _TEMPLATE_ITEM_TYPE_ = String;

    {$INCLUDE ‘GenericMappingInterface.inc’} //<- file where you write declarations using references to types names _TEMPLATE_KEY_TYPE_ and _TEMPLATE_ITEM_TYPE_

    TMappingIntegerString = _TEMPLATE_MAPPING_TYPE_ //<- likewise, you choose in GenericMappingInterface.inc the convention for the name of the resulting class/record/…

    IMPLEMENTATION

    {$INCLUDE ‘GenericMappingImplementation.inc’} //<- file containing your template ipmplementation, using the same name conventions as above

    END.

    We are making extensive use of this feature with our Delphi projects.
    The gain is pretty straightforward.
    The main drawback with this method is that you have to explicitly create a separate Unit file for each instance of your template, since declaring 2 different versions of a template -e.g. MappingIntegerString and MappingIntegerInteger- in the same unit would result in a name conflict with the types.
    With this method, you have the same debugging issues as templates in C++ : you potentially have different bugs for different instances of your template, caused for various reasons, and the compiler is obviously not adapted to check types and coherence in partial source files (in this case : the two .inc files used).

    We are currently moving to Delphi 2009, and are pretty excited with the Generic type features for win32 (our projects are all for win32 platform).
    However, we already saw several drawbacks :
    -first, the CodeGear team did a terrific job at including generic types in a compiled language, but there are still too many bugs in the implementation (the first released version prevented to use pointers on generic types, there are still some strange compiling issues with method declaration…) ;
    -second, we made some performance test, and going through *runtime* interfaces for generic types just doesn’t make it - we will still have to use our home brewed templates for performance issues in computation intensive parts.

    Though I am really looking forward to use the neat genric syntax, I will go with Sergey : why, in a *compiled* language which produces compiled, typefree, static asm code do we have to pay a *runtime* overhead for static type constrains?

Post a Comment

Your email is never published nor shared. Required fields are marked *

Bad Behavior has blocked 713 access attempts in the last 7 days.

Close