Making a linked-list with generics - delphi

I have read how to make a pointer to a normal class and use it inside the class difinition:
type
PExample = ^TExample;
TExample = class
data: Integer;
next: PExample;
end;
but how do you do it with templatized parameters? This does not compile with the error Undeclared identifier: 'TExample' on the second line:
type
PExample = ^TExample;
TExample<T> = class
data: T;
next: PExample;
end;
Changing it to
PExample = ^TExample<T>;
does not fix it.

As you are using a class, you don't need to use a PExample. Classes are already reference types.
TExample<T> = class
data: T;
next: TExample<T>;
end;
This should work without the need to declare any pointer types. Pointer types are only required if you work with records (which are value types).
EDIT:
To my surprise I just noticed that this compiles and works in Delphi XE:
program Project;
{$APPTYPE CONSOLE}
type
TNode<T> = record
Next: ^TNode<T>;
Data: T;
end;
var
Node1, Node2: TNode<Integer>;
begin
Node1.Next := #Node2;
Node1.Data := 1;
Node2.Next := nil;
Node2.Data := 2;
WriteLn(Node1.Data);
WriteLn(Node1.Next.Data);
end.
It does still not solve the problem of defining a general generic pointer type, because this:
PNode<T> = ^TNode<T>;
does not work.

The other answers tell you how to build a generic linked list of classes. If you ever need to build a generic linked list of records, you cannot do so at present:
type
PNode<T> = ^TNode<T>;
TNode<T> = record
public
Next: PNode;
end;
does not compile.
Nor does:
type
TNode<T> = record
type
PNode = ^TNode;
public
Next: PNode;
end;
I believe that the reason for this is that the single-pass compiler does not support forward declarations of methods. This is actually more of a practical problem for operator overloading than for generics because classes can be used for generic linked lists.

Is it necessary to use a pointer to a class? Using a class reference for your "next" field should work, like this:
type
TExample<T> = class
data: T;
next: TExample<T>;
end;
The "next" field can still be NIL or can be assigned another TExample instance. This seems to negate the need of using a traditional ^ pointer (although you could argue that a reference to a class is also a pointer, just with different syntax).

Related

Delphi property getter function using generics

It would be nice to have generic property getters/setters to perform a common task on each access.
That code gives a compile-time error in Delphi XE2 'E2008 Incompatible types'. A similar code gave an internal error during compilation, but never compiles. Do I make a mistake or it is a compiler limitation?
type TFoo = class
private
function Get<T>: T;
public
property Bar: Integer read Get<Integer>;
end;
function TFoo.Get<T>: T;
begin
Result := 0;
end;
The following things can be generic in the Delphi language:
classes, e.g. TFooClass<T> = class
records, e.g. TFooRecord<T> = record
interfaces, e.g. TFooInterface<T> = interface
procedural types, e.g. TFooProc<T> = procedure
methods, e.g. procedure FooMethod<T>()
Properties cannot be generic themselves, and cannot be implemented using generic getter or setter methods.

Forward declarations for record types (or arrays)

I want to do this in XE5:
type
TMyRec = record
// fields
class function GetList: TMyRecArr; static;
end;
TMyRecArr = array of TMyRec;
I've already seen "Forward declarations for record types" and "how to do a typed forward declaration?", but they seem irrelevant since my problem is not passing the record as a parameter.
You cannot use a forward declaration to declare a record type or an array type. But not to fear. You can use a generic dynamic array, TArray<T>.
type
TMyRec = record
class function GetList: TArray<TMyRec>; static;
end;
This is actually better than declaring TMyRecArr as per the code in your question. That's because the generic TArray<T> has more flexible type identity than a traditional dynamic array type. You can use TArray<T> with generic types defined in libraries that are independent and unaware of your code.
Now, you could declare the type like this:
type
TMyRec = record
type TMyRecArray = array of TMyRec;
class function GetList: TMyRecArray; static;
end;
And then your array type is TMyRec.TMyRecArray. But I urge you not to do this. You'll have a type that can only be used with you code, and cannot be used with third party code.
In summary, TArray<T> is your friend.
Since declarations of pointers types are permitted before the type definition, slightly modifying your function you're allowed to do this:
type
PMyRecArr = ^TMyRecArr;
TMyRec = record
// fields
class procedure GetList(const arr: PMyRecArr); static;
end;
TMyRecArr = array of TMyRec;
The procedure implementation and its usage follow:
class procedure TMyRec.GetList(const arr: PMyRecArr);
begin
SetLength(arr^, 4);
end;
var
arr: TMyRecArr;
begin
TMyRec.GetList(#arr);
Writeln(Length(arr));//prints 4
end.
Well, I searched a little more, and found that I can use helpers to do this:
type
TMyRec = record
// fields
end;
TMyRecArr = array of TMyRec;
TMyRecHelper = record helper for TMyRec
class function GetList: TMyRecArr; static;
end;
Sure it lacks benefits of generics that David mentioned in his answer and comments, but it doesn't make the code auto-complete unusable! I mean one may comes to this conclusion that the flexibility that TArray<T> offers is not needed in some piece of code. Then it would be nothing more than increment in memory usage of the final running application or possibly lower performance.
Instead of trying to add forward decalrations to records (which up until now still isn't possible in Delphi), there is a way around this : move functions that reference another record type towards a record helper, declared where both record types are in scope.
type
RecordA = record
// [...]
end;
RecordB = record
// [...]
end;
RecordAHelper = record helper for RecordA
procedure Call(argument: RecordB);
end;
After obvioulsy implementing this, above allows one to write (and compile and run) : RecordA_variable.Call(RecordB_variable)

How to assign an empty set to a record using overloaded operators

I'm using a record to encapsulate two dissimular sets.
I've put in operators to allow the assignment of either set to the record. Doing so will clear the other set.
However I cannot assign an empty set.
See the following example code:
Program test;
{$Apptype console}
type
TSomeThing = (a,b,c);
TOtherThing = (x,y,z);
TSomeThings = set of TSomething;
TOtherThings = set of TOtherThing;
TSomeRecord = record
strict private
Fa: TSomeThings;
Fb: TOtherThings;
public
class operator Implicit(a: TSomeThings): TSomeRecord;
class operator Implicit(a: TOtherThings): TSomeRecord;
end;
implementation
class operator TSomeRecord.Implicit(a: TSomeThings): TSomeRecord;
begin
Result.Fa:= a;
Result.Fb:= [];
end;
class operator TSomeRecord.Implicit(a: TOtherThings): TSomeRecord;
begin
Result.Fa:= [];
Result.Fb:= a;
end;
var
SomeRec: TSomeRecord;
begin
SomeRec:= [];
end.
[dcc64 Error] InstructionList.pas(512): E2010 Incompatible types: 'TSomeRecord' and 'Set'
How do I make it so I can assign the empty set to my record?
I can misuse the implicit operator to allow SomeRec:= nil;, but that looks very ugly.
The compiler cannot tell whether you mean an empty set of TSomeThing or an empty set of TOtherThing. You can declare typed constants to allow the compiler to resolve the overload:
const
EmptySomeThings: TSomeThings = [];
EmptyOtherThings: TOtherThings = [];
Then the following assignments compile and resolve as you would expect:
SomeRec:= EmptySomeThings;
SomeRec:= EmptyOtherThings;
Of course, you know that either one of these has the same effect, because the implementation of the Implicit operators sets one field, and clears the other. But the compiler cannot know this.
If you wish to clear both members of the record you can always use:
SomeRec:= Default(TSomeRecord);
I personally might wrap that up in a static class method like this:
class function Default: TSomeRecord; static;
....
class function TSomeRecord.Default: TSomeRecord;
begin
Result := Default(TSomeRecord);
end;
Then you can write:
SomeRec:= TSomeRecord.Default;
In an ideal world you'd be able to declare a constant in the type, but the language designers did not think of that and it is sadly not possible.
Update
Rudy correctly points out in a comment, that constants can be added to a record type by way of a record helper. This was news to me as I mistakenly believed that helpers could only add methods. This is what I love about Stack Overflow. Even when you think you know something pretty well, there's always scope for more knowledge to be acquired. Thanks Rudy.
So you might write:
type
TSomeRecordHelper = record helper for TSomeRecord
public
const
Default: TSomeRecord = ();
end;

Equal operator in Generic holding Records in Delphi

I have a list of generics in which I want to put either some records or some classes
TMyList<T> = class
private
fCount: Cardinal;
fItems: array of T;
public
constructor Create(aSize: Integer);
procedure UpdateItem(const x: T);
end;
But I can't have the compiling
procedure TMyList<T>.UpdateItem(const x: T);
var
I: integer;
begin
for I := 0 to fCount - 1 do
if fItems[I] = x then begin // <- error E2015
//do update
break;
end;
end;
It works for classes with this declaration: TMyList<T : class> = class, but then it can't hold records anymore.
Of course, for the record I declare class operator Equal(Left, Right : TMyRecord) : Boolean; so that MyRecord1 = MyRecord2 would compile.
This can never be made to work using the = operator. The reason is that generic constraints are not rich enough to specify the availability of operators. You simply cannot use the = operator on generic operands.
You can do so if you constrain the operands to be a class, because a classes are references, and the compiler knows how to compare references for equality. Basically the compiler needs to know how to generate the code when it compiles the generic class. Unlike C++ or Smalltalk templates, with generics the compiler does not wait until instantiation to compile the code.
If you want to use a custom comparer then you will need to supply that explicity. Which is rather frustrating, I know. If you can make do with the default comparer you can use:
TEqualityComparer<T>.Default

TGenericClass<T> containing a TObjectList<T> doesn't compile

I'm trying to write a generic class which contains a generic TObjectList< T > which should contain only Elements of TItem.
uses
Generics.Collections;
type
TItem = class
end;
TGenericClass<T: TItem> = class
public
SimpleList: TList<T>; // This compiles
ObjectList: TObjectList<T>; // This doesn't compile: Compiler complaints that "T is not a class type"
end;
Is this a wrong syntax? BTW: TGenericClass< T: class> compiles, but then the Items in the List are not TItem anymore which is what I don't want.
This is a known bug with the D2009 compiler. It will most likely be fixed soon, either in an update or hotfix for 2009, or in Delphi 2010 (Weaver) once it gets released. Until then, you need some sort of workaround, unfortunately. :(
Generic types can have several constraints:
A class name, the generic type must be of that class of a descendant of the class.
An interface name, the generic type must implement that interface.
'class', the generic type must be a class (this can't be combined with a classname).
'record', the generic type must be a record.
'constructor', a bit vague, but you can create instances of the generic class type.
If you create a generic that uses other generics, you need to copy the constraints, else it won't work. In your case, TObjectList has the class constraint. This means, your T needs that constraint too.
Unfortunately this can't be combined with the named class constraint.
So I advice you to use an interface, these can be combined:
type
IItem = interface end;
TItem = class (TInterfacedObject, IItem) end;
TGenericClass<T: class, IItem> = class
private
FSimpleList: TList<T>;
FObjectList: TObjectList<T>;
end;
Besides, you should make your fields private else anyone can change them.
I like GameCat's answer (gave it +1) for the description of class constraints.
I have a slight modification of your code that works. Note that since you gave a constraint to say that T must be a descendant of TItem, you can actually just declare ObjectList as TObjectList<TItem> - no need to use T here.
Alternatively, you could create a proxy of sorts. First, note GameCat's comment about fields being private.
type
TGenericClass<T: TItem> = class
private
type
D = class(TItem); // Proxy to get your T into and object list
private
SimpleList: TList<T>;
ObjectList: TObjectList<D>; // Compiles now, but there is that type issue
public
procedure Add(Item: T); // No direct access to ObjectList
end;
Add is an example of how to access the object list. As it turns out, you can pass Item to ObjectList.Add with no trouble whatsoever:
procedure TGenericClass<T>.Add(Item: T);
begin
ObjectList.Add(Item);
end;
I think that may be a bug though, so to protect yourself against that getting fixed:
procedure TGenericClass<T>.Add(Item: T);
var
Obj: TObject;
begin
Obj := Item;
ObjectList.Add(D(Obj));
end;
Given your scenario though, I'd say TObjectList should do just fine.

Resources