views:

94

answers:

4

This is kind of a follow-up from my other question.

When I first heard about generics, it was before the release of Delphi 2009 (Where they introduced it first). I know it was supported in .Net before that, but I have yet to dig in that realm.

Reading about generics, I learned that it allowed class to have a variable argument to it, and that whatever value you passed to it would then be replaced through all the code of the class.

The way generics were described (or at least, what I understood generics allowed) was that, given the following declaration:

procedure TMyClass<T>.Init;
begin
  FField := T.Create(nil);
end;

I assumed it would work. I assumed where the compile would fail is as follow:

begin
  TMyClass<TComponent>.Create; //Works correctly
  TMyClass<TObject>.Create;  //Doesn't work, as even though it HAS a constructor, it has none that receive a single pointer parameter
  TMyClass<string>.Create; //Doesn't work, not an object. 
end;

Now, I well know I was wrong. So, what I wonder now, is there a technology/language feature that would support such a construct. Code templates perhaps? Generics in other programming languages? Or maybe something else?

A: 

You can put constraint(s) on a generic type. You need this if you want to use certain aspects of that type. For example a method.

If you want to call a constructor, you need to give the consructor constraint next to the class constraint:

type
  TMyClass<T: TComponent, constructor> = class
    // ..
  end;

procedure TMyClass<T>.Init;
begin
  FField := T.Create(nil);
end;

Unfortunately TObject isn't a valid constraint. (according to Delphi XE).

Now, I well know I was wrong. So, what I wonder now, is there a technology/language >feature that would support such a construct. Code templates perhaps? Generics in other >programming languages? Or maybe something else? This can be risky or even meaningless. If you call method X on the generic and you instantiate it with a class that doesn't support method X, what is the correct behaviour...

Gamecat
The constructor constraint only allow to call for a parameterless constructor. Even setting TComponent as a constraint won't allow to call TComponent's constructor as it has 1 parameter.
Ken Bourassa
+1  A: 

Now, I well know I was wrong. So, what I wonder now, is there a technology/language feature that would support such a construct. Code templates perhaps? Generics in other programming languages? Or maybe something else?

Generics in C# have the power that you want. Templates in C++ are even stronger - code generated via template is identical to code written by hand, except for the part where they can only be compiled inline, which sucks.

DeadMG
Correct me if I'm wrong, but templates in C++ have the drawback of being "defined" in the scope of the unit where it is used. So unit A.TMyClass<TComponent> would be incompatible with unit B.TMyClass<TComponent>. Right?
Ken Bourassa
In .NET, both generics and the unified typing system (everything, including value types descend from System.Object) are supported at the IL level, so almost any language on the .NET platform (including Delphi Prism, VB.NET and many others) supports both, not only C#.
Jeroen Pluimers
@Ken: You're wrong. @Jeroen: That's true. C# does have generics, so I'm not wrong, but I could have been more correct.
DeadMG
ok, went back to read the article I read, and it's not between unit, but between assembly they are incompatible... My bad. (For reference: http://www.developer.com/net/net/article.php/3367531/Comparing-NET-Generics-and-C-Templates.htm)
Ken Bourassa
@Ken: C++ doesn't have assemblies. Template types are only incompatible between anything if they have incompatible definitions. Templates are not special in C++- they are no more or less compatible or incompatible than regular classes, except you have to ship the source.
DeadMG
+1  A: 

@Gamecat, you cannot have TObject as a constraint, but you can have class as a constraint (which nicelly covers that lack of TObject constraint).

Note that no matter if you use TObject or class, you cannot call the Create with a parameter without a trick.

Example 1: class constraint:

unit Unit1;

interface

uses
  Classes;

type
  TMyClass<T: class, constructor> = class
  strict private
    FField: T;
  public
    procedure Init;
  end;

implementation

procedure TMyClass<T>.Init;
begin
  FField := T.Create();
end;

end.

Example 2: TComponent as a constraint, and parameter in the Create

unit Unit2;

interface

uses
  Classes;

type
  TMyClass<T: TComponent, constructor> = class
  strict private
    FField: T;
  public
    procedure Init;
  end;

implementation

procedure TMyClass<T>.Init;
var
  ComponentClass: TComponentClass;
begin
  ComponentClass := T;
  FField := ComponentClass.Create(nil);
end;

end.

In addition to the class constraint, you can also have a record constraint. With that, you need the Default to initialize fields:

unit Unit3;

interface

uses
  Classes;

type
  TMyClass<T: record> = class
  strict private
    FField: T;
  public
    procedure Init;
  end;

implementation

procedure TMyClass<T>.Init;
begin
  FField := Default(T);
end;

end.

Hope that sheds some light on generics and constraints.

--jeroen

Jeroen Pluimers
@Jeroen Pluimers, I know, but I saw (too late) the question was not on how to get it to work but if there are languages that allowed the construct (see last paragraph).
Gamecat
+1 for the ComponentClass.Create(nil); trick. That really made my day. Very nice workaround the lack of proper (or more evolved) constructor constraint.
Ken Bourassa
@Gamecat: I wondered about the reason, thanks for clarifying it. And no worries, I'm often not a "good question reader" myself :-) - note I put two answers in to address most parts of the question.
Jeroen Pluimers
A: 

@Ken: In order for code like you requested to work in a real generic way, you would need to have a unified typing tystem that merges both reference types (classes) and value types (strings, integers, etc).

Historically, native Delphi doesn't have such a typing system (.NET has, and generics in Delphi Prism supports it, just as C# and VB.NET do).

Working around that is difficult; Allen Bauer gave it a shot implementing a Nullable type, and he had to do some serious twisting to implement only the Equals (=) and NotEquals (<>) operator behaviour in a way that covers both reference and value types.

So supporting these will be tough, but probably doable:

begin
  TMyClass<TComponent>.Create; //Works correctly
  TMyClass<TObject>.Create;  //Doesn't work, as even though it HAS a constructor, it has none that receive a single pointer parameter
  TMyClass<string>.Create; //Doesn't work, not an object.
end;

--jeroen

Jeroen Pluimers