They say Generics and pointers don’t mix.
type
PMyThing = ^TMyThing // [DCC Error] E2508 type parameters not allowed on this type
TMyThing = record
Thing: T;
end;
Ok, they don’t. But there is a loophole!
type
TMyThing = record
Thing: T;
NextThing: ^TMyThing;
end;
Why this is allowed, I don’t know. Just like I don’t really understand why the first one is forbidden. There is probably some good explanation for it.
Still – it can be fun breaking the rules!
unit GenericDoubleLinkedList;
interface
uses
Classes, Generics.Defaults;
type
TLinkVisitor = reference to procedure(const Item: T);
TDoubleLinked = record
Value: T;
PrevLink: ^TDoubleLinked; // Hey, it compiles!
NextLink: ^TDoubleLinked;
constructor Create(aValue:T);
function Add(aValue:T): TDoubleLinked;
function HasNext:Boolean;
function Next: TDoubleLinked;
function HasPrev:Boolean;
function Prev: TDoubleLinked;
function First: TDoubleLinked;
function Last: TDoubleLinked;
procedure ForEach(const Proc: TLinkVisitor);
end;
procedure Test(const Log:TStrings);
implementation
{ TDoubleLinked }
constructor TDoubleLinked.Create(aValue: T);
begin
Value := aValue;
NextLink := nil;
PrevLink := nil;
end;
function TDoubleLinked.Add(aValue: T): TDoubleLinked;
var
p: ^TDoubleLinked; // But this one is not assignment compatible
begin
p := AllocMem(SizeOf(TDoubleLinked)); // Make space
p^ := Self; // Copy current value to allocated block
Value := aValue; // Set self to new value
p.NextLink := @Self;
if Assigned(p.PrevLink) // Fix up previous nextlink
then Pointer(p.PrevLink.NextLink) := Pointer(p);
Pointer(PrevLink) := Pointer(p); // Point back to old value
Result := Self;
end;
function TDoubleLinked.HasPrev: Boolean;
begin
Result := PrevLink nil;
end;
function TDoubleLinked.Prev: TDoubleLinked;
begin
Result := TDoubleLinked(PrevLink^)
end;
function TDoubleLinked.HasNext: Boolean;
begin
Result := NextLink nil;
end;
function TDoubleLinked.Next: TDoubleLinked;
begin
Result := TDoubleLinked(NextLink^)
end;
function TDoubleLinked.First: TDoubleLinked;
begin
Result := Self;
while Result.HasPrev
do Result := Result.Prev;
end;
function TDoubleLinked.Last: TDoubleLinked;
begin
Result := Self;
while Result.HasNext
do Result := Result.Next;
end;
procedure TDoubleLinked.ForEach(const Proc: TLinkVisitor);
var
Node: TDoubleLinked;
begin
Node := First;
Proc(Node.Value);
while Node.HasNext
do begin
Node := Node.Next;
Proc(Node.Value);
end;
end;
procedure Test(const Log:TStrings);
var
List, Node : TDoubleLinked;
begin
List.Create('One');
List.Add('Two');
List.Add('Three');
Node := List; // Bad idea
List.Add('Four');
Node.Add('ThreeAndAHalf');
List.ForEach(
procedure(const Value:String)
begin
Log.Add('List: ' + Value)
end);
Node.ForEach(
procedure(const Value:String)
begin
Log.Add('Node: ' + Value)
end);
end;
end.
The problem is that “List” is not a pointer, but the tail item of the list. Hence, a Delete procedure needs to take this into consideration.
Even worse, if you add a second Node variable to point to something in the list, that reference will not be fixed up after adding ‘Four’, and hence it will take the tail place of the list – for both references, effectively forgetting the ‘Four’ item.
So, although this was somewhat entertaining, a mix of Generics and pointers probably isn’t something we should make use of.
Exercise for the reader: Implement the TDoubleLinked.Delete; procedure.
End question: Why are we not allowed to declare pointers to generic types?
You must be logged in to post a comment.