Recent

Author Topic: [Solved] Generics fighting back.  (Read 3461 times)

Tomu

  • New Member
  • *
  • Posts: 45
[Solved] Generics fighting back.
« on: August 01, 2025, 09:38:58 pm »
New to Generics.

I'm trying to add a custom/user defines sort to a  TList<string>;
The code below compiles but blows up on :  Slist.Sort(TStringComparer(@compare)) ;

"Project generics raised exception class 'External: SIGSEGV'.

 In file 'generics.collections.pas' at line 1018"

What am I doing wrong?
Thanks.

Code: Pascal  [Select][+][-]
  1. uses Generics.Collections, Generics.Defaults;
  2.  
  3. {$R *.lfm}
  4.  
  5. { TForm1 }
  6.  
  7.  
  8.  
  9. procedure TForm1.Button1Click(Sender: TObject);
  10. type
  11.  
  12.   TStrStack = specialize TStack<string>;
  13.   TStrQue   = specialize TQueue<string>;
  14.   TMTyList   = specialize TList<string>;
  15.  
  16.   TStringComparer = specialize TComparer<string>;
  17.  
  18.  
  19.   function compare(a , b : string) : Integer;
  20.   begin
  21.       result := CompareText(a,b);
  22.   end;
  23.  
  24.  
  25. var
  26.    SStack : TStrStack ;
  27.    MyQue  : TStrQue;
  28.    Slist  : TMTyList;
  29.    i : integer;
  30.    s : string;
  31.  
  32. begin
  33.  
  34.    SStack := TStrStack.create;
  35.    MyQue  := TStrQue.create;
  36.    Slist  := TMTyList.Create;
  37.  
  38.    for i := 1 to 10 do
  39.     begin
  40.       SStack.Push(inttostr(i));
  41.       MyQue.Enqueue(inttostr(i));
  42.       Slist.Add(inttostr(i));
  43.     end;
  44.  
  45.     while SStack.Count > 0 do
  46.     begin
  47.       s := SStack.pop;
  48.       listbox1.Items.add(s);
  49.     end;
  50.  
  51.     while MyQue.Count > 0 do
  52.     begin
  53.       s := MyQue.Dequeue;
  54.       listbox2.Items.add(s);
  55.     end;
  56.  
  57.     for S in Slist do
  58.       memo1.Lines.add(s);
  59.  
  60.      Slist.Sort(TStringComparer(@compare)) ;
  61.  
  62.     Slist.Sort;
  63.     for S in Slist do
  64.       memo1.Lines.add(s);
  65.  
  66. end;
  67.  
  68. end.
« Last Edit: August 07, 2025, 09:23:05 pm by Tomu »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12561
  • Debugger - SynEdit - and more
    • wiki
Re: Generics fighting back.
« Reply #1 on: August 01, 2025, 10:22:51 pm »
Please use [ code=pascal ] {your code} [ /code ].
Thanks

(I edited your post and added it for you.)

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12561
  • Debugger - SynEdit - and more
    • wiki
Re: Generics fighting back.
« Reply #2 on: August 01, 2025, 10:31:45 pm »
Code: Pascal  [Select][+][-]
  1.          Slist.Sort(TStringComparer(@compare)) ;

TStringComparer  is actually a class.
Code: Pascal  [Select][+][-]
  1. TStringComparer = specialize TComparer<string>;
  2. TComparer<T> = class(TInterfacedObject, IComparer<T>)

"Compare" on the other hand is a function (and a nested function to be exact).

So that typecast can not work. You can not cast a function into a class.

Correct code should not need any type casts.



For all else, I haven't worked with that part of generics. So the only way for me to give more of an answer would be to read up the documentation... (But that you should be able to do too / and I would equally have to google where it is )

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12561
  • Debugger - SynEdit - and more
    • wiki
Re: Generics fighting back.
« Reply #3 on: August 01, 2025, 10:37:31 pm »
Stopped it in the debugger, it crashes when calling
Code: Pascal  [Select][+][-]
  1. cmp.compare
which can be traced to the "compare" method of TComparer.


From a quick go through the code

Code: Pascal  [Select][+][-]
  1.   TComparer<T> = class(TInterfacedObject, IComparer<T>)
  2.   public
  3.     class function Default: IComparer<T>; static;
  4.     function Compare(const ALeft, ARight: T): Integer; virtual; abstract; overload;
  5.  

the compare function is abstract.

So just subclass the comparer. Or just do your own class implementing "IComparer" as  TList<>.Sort takes the interface
Code: Pascal  [Select][+][-]
  1. procedure Sort(const AComparer: IComparer<T>); overload;


Something like
Code: Pascal  [Select][+][-]
  1.  TMyStringComparer = class(TInterfacedObject, IComparer<String>)
  2.   public
  3.     function Compare(const ALeft, ARight: String): Integer;
  4. end;
  5.  

And implement the comparison in that.

You need to instantiate it "TMyStringComparer .Create"

I haven't tested it
« Last Edit: August 01, 2025, 10:39:37 pm by Martin_fr »

jamie

  • Hero Member
  • *****
  • Posts: 7855
Re: Generics fighting back.
« Reply #4 on: August 02, 2025, 02:01:08 am »
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode Delphi}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
  9.   Generics.Collections, Generics.Defaults;
  10.  
  11. type
  12.  
  13.   { TForm1 }
  14.  
  15.   TForm1 = class(TForm)
  16.     Button1: TButton;
  17.     ListBox1: TListBox;
  18.     ListBox2: TListBox;
  19.     Memo1: TMemo;
  20.     procedure Button1Click(Sender: TObject);
  21.   private
  22.  
  23.   public
  24.  end;
  25.  
  26.   TMyCompar<T> = Class(TInterfacedObject, IComparer<T>)
  27.     Public
  28.      function Compare(constref ALeft, ARight: T): Integer; virtual;overload;
  29.   end;
  30.  
  31. var
  32.   Form1: TForm1;
  33.  
  34. implementation
  35.  
  36. {$R *.lfm}
  37. Function TMyCompar<T>.Compare(constref ALeft, ARight: T): Integer;
  38. Begin
  39.   result := CompareText(aLeft,aRight);
  40. End;
  41. { TForm1 }
  42. procedure TForm1.Button1Click(Sender: TObject);
  43.   type
  44.   TStrStack = TStack<string>;
  45.   TStrQue   = TQueue<string>;
  46.   TMTyList   =TList<string>;
  47. var
  48.    SStack : TStrStack ;
  49.    MyQue  : TStrQue;
  50.    Slist  : TMTyList;
  51.    i : integer;
  52.    s : string;
  53.    ComparString:TMyCompar<String>;
  54. begin
  55.    ComparString := TMyCompar<String>.Create;
  56.    SStack := TStrStack.create;
  57.    MyQue  := TStrQue.create;
  58.    Slist  := TMTyList.Create;
  59.  
  60.    for i := 1 to 10 do
  61.     begin
  62.       SStack.Push(inttostr(i));
  63.       MyQue.Enqueue(inttostr(i));
  64.       Slist.Add(inttostr(i));
  65.     end;
  66.  
  67.     while SStack.Count > 0 do
  68.     begin
  69.       s := SStack.pop;
  70.       listbox1.Items.add(s);
  71.     end;
  72.  
  73.     while MyQue.Count > 0 do
  74.     begin
  75.       s := MyQue.Dequeue;
  76.       listbox2.Items.add(s);
  77.     end;
  78.  
  79.     for S in Slist do
  80.       memo1.Lines.add(s);
  81.      Slist.Sort(ComparString);
  82.  
  83.     Slist.Sort;
  84.     for S in Slist do
  85.       memo1.Lines.add(s);
  86.    SStack.free;
  87.    MyQue.free;
  88.    SList.Free;
  89.    ComparString.free;
  90. end;
  91.  
  92. end.
  93.  
  94.  

Tested and Edited.

jamie
The only true wisdom is knowing you know nothing

Tomu

  • New Member
  • *
  • Posts: 45
Re: Generics fighting back.
« Reply #5 on: August 02, 2025, 02:10:34 pm »
Thanks for the working example!
I see you added the compiler directive {$mode Delphi}{$H+}

Using generics is a bit confusing to me. There seem to be 2 (or 3!) ways!
Generics.Collections, FGL, and lgenerics

And two compiler directivs {$mode objfpc}{$H+}  and {$mode Delphi}{$H+}.

Again, Thank you for your help.

Ștefan-Iulian Alecu

  • New Member
  • *
  • Posts: 30
Re: Generics fighting back.
« Reply #6 on: August 02, 2025, 02:33:04 pm »
Thanks for the working example!
I see you added the compiler directive {$mode Delphi}{$H+}

Using generics is a bit confusing to me. There seem to be 2 (or 3!) ways!
Generics.Collections, FGL, and lgenerics

And two compiler directivs {$mode objfpc}{$H+}  and {$mode Delphi}{$H+}.

Again, Thank you for your help.

Here's the gist of it.

For the modes, objfpc indicates the Free Pascal dialect of the Object Pascal language, and you can probably guess what delphi does. ;) It is a matter of personal preference, although objfpc, being the main dialect, will receive new features faster.

Generics.Collections is the newest of all of these. It comes from rtl-generics, which is a larger, somewhat more featureful collection of generic containers written in {$mode delphi}, intended to be compatible with the Delphi counterpart. It is included since FPC 3.1.1+, but there's a version for 3.0.4 too. Since it is pretty much one to one (or at least as close as it can be), you can just use the Delphi docs. I personally consider the API of Generics.Collections to be cleaner and has more features, so it's the one I default to.

FGL, or Free Generics Library, is a FPC-native collection of generic containers written in {$mode objfpc}. IMO, it's slightly more cumbersome to use, but it was there first, so there's that. It gets the job done, so you can still see it in some code around.

As for lgenerics, it is an external package made by A. Koverdyaev (avk) and requires FPC 3.2.2+ and Lazarus 2.2+. It is the king when it comes to anything tree/graph-related. It has the most features, some not related to data structures at all, such as 128 bit integers, non-cryptographic hashes, channels and thread pools, as well as full support for JSON (including serializing JSON from native data structures using RTTI) and CSV. It seems to be pretty performant too, so it isn't a bad choice.

If you have FPC 3.2.2 and you only need things like dictionaries, stacks, queues, thread lists or lists, then Generics.Collections is the way to go. Otherwise, if you're fine with external dependencies, lgenerics is a good choice. Otherwise, have fun with FGL.

jamie

  • Hero Member
  • *****
  • Posts: 7855
Re: Generics fighting back.
« Reply #7 on: August 02, 2025, 03:20:34 pm »
Personally, I prefer using the FGL generics because they work as advertised and seem to keep working in large apps.

 For the Delphi versions, I've found some unexplainable behaviors in code when using them and also for any serious applications, the compiler seems to have issues where it loses track of some compile time variables that are created during the creation of the generics and linking.
 
 These errors normally show at the end of the main file and points to nowhere which I assume is due to the compiler getting swamped or maybe some combination of generics is showing a bug that wasn't possibly known at that time with a proper crash report.

 Who knows, but in the end, I've found the FGL works even with extended generics I've created to mimic known templates in other languages.

 As for the collections, they do seem to work ok in Delphi but creates a heavy app for what should be simple.

Jamie
The only true wisdom is knowing you know nothing

Tomu

  • New Member
  • *
  • Posts: 45
Re: Generics fighting back.
« Reply #8 on: August 03, 2025, 06:58:51 pm »
Instead of wrestling with the 'built in' compare, I copied a Shell sort example and adapted it to the generated/specialed TList<TMyRecord>;
 This may be a less pure way to do it, but for me it was much easier.

Code: Pascal  [Select][+][-]
  1.  {
  2. unit Unit1;
  3.  
  4. {$mode objfpc}{$H+}
  5. {$modeswitch advancedrecords}
  6.  
  7. interface
  8.  
  9. uses
  10.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  11.  
  12. type
  13.  
  14.   { TForm1 }
  15.  
  16.   TForm1 = class(TForm)
  17.     Button1: TButton;
  18.     ListBox1: TListBox;
  19.     procedure Button1Click(Sender: TObject);
  20.   private
  21.  
  22.   public
  23.  
  24.   end;
  25.  
  26. var
  27.   Form1: TForm1;
  28.  
  29. implementation
  30. {$R *.lfm}
  31.  
  32. uses
  33.   Generics.Collections, Generics.Defaults;
  34.  
  35. type
  36.  
  37.   TMyRecord = packed record
  38.     ID: Integer;
  39.     Name: String;
  40.     function show: string;
  41.   end;
  42.  
  43.   TMyList = specialize TList<TMyRecord>;
  44.  
  45.  function TMyRecord.show: string;
  46.  begin
  47.    Result := IntToStr(ID) + ' : ' + Name;
  48.  end;
  49.  
  50. { TForm1 }
  51.  
  52. function CompareTMyRecord(const L, R : TMyRecord):  integer ;
  53. Begin
  54.     if l.ID > R.ID Then Result := 1
  55.     else if l.ID < R.ID Then Result := -1
  56.     else Result := 0;
  57. End;
  58.  
  59. procedure ShellSortList(aList: TMyList);
  60.    var
  61.      i, j, gap: Integer;
  62.      temp: TMyRecord;
  63.    begin
  64.      gap := aList.Count div 2; // Initial gap
  65.      while gap > 0 do
  66.      begin
  67.        for i := gap to aList.Count - 1 do
  68.        begin
  69.          temp := aList[i];
  70.          j := i;
  71.          while (j >= gap) and (CompareTMyRecord(aList[j - gap], temp) > 0) do
  72.          begin
  73.            aList[j] := aList[j - gap];
  74.            j := j - gap;
  75.          end;
  76.          aList[j] := temp;
  77.        end;
  78.        gap := gap div 2; // Reduce gap
  79.      end;
  80.    end;
  81.  
  82. procedure TForm1.Button1Click(Sender: TObject);
  83. var
  84.  R : TMyRecord;
  85.  L : TMyList;
  86.  i : integer;
  87.  s : string;
  88. begin
  89.  
  90.  L :=  TMyList.Create;
  91.  
  92.  R.ID   := 7;
  93.  R.Name := 'Tom';
  94.  L.Add(R);
  95.  
  96.  R.ID   := 2;
  97.  R.Name := 'Fred';
  98.  L.Add(R);
  99.  
  100.  R.ID   := 17;
  101.  R.Name := 'Bill';
  102.  L.Add(R);
  103.  
  104.  
  105.  for i := 0 to L.Count -1 do
  106.  begin
  107.    s := L[i].show;
  108.    ListBox1.Items.Add(s);
  109.  end;
  110.  
  111.  L.Exchange(1,2);
  112.  ListBox1.Items.Add('-----');
  113.   for i := 0 to L.Count -1 do
  114.  begin
  115.    s := L[i].show;
  116.    ListBox1.Items.Add(s);
  117.  end;
  118.  
  119.  
  120.   ShellSortList(L);
  121.  
  122.   ListBox1.Items.Add('-----');
  123.   for i := 0 to L.Count -1 do
  124.  begin
  125.    s := L[i].show;
  126.    ListBox1.Items.Add(s);
  127.  end;
  128.  
  129. L.Free;
  130.  
  131. end;
  132.  
  133. end.
  134. }
.
« Last Edit: August 03, 2025, 07:25:03 pm by Martin_fr »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12561
  • Debugger - SynEdit - and more
    • wiki
Re: Generics fighting back.
« Reply #9 on: August 03, 2025, 07:24:42 pm »
[ code ] tags => without the space. Sorry, I did not mention that before, I added the spaces, so the forum would not execute them....

440bx

  • Hero Member
  • *****
  • Posts: 6560
Re: Generics fighting back.
« Reply #10 on: August 03, 2025, 08:05:08 pm »
[ code ] tags => without the space. Sorry, I did not mention that before, I added the spaces, so the forum would not execute them....

I learned the following trick from PascalDragon that is made to order for that situation:

Also, side note, you can use [nobbc][/nobbc] to display tags without the forum interpreting them, e.g. [code][/code], so you don't need to add spaces.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

dseligo

  • Hero Member
  • *****
  • Posts: 1686
Re: Generics fighting back.
« Reply #11 on: August 03, 2025, 09:12:06 pm »
[ code ] tags => without the space. Sorry, I did not mention that before, I added the spaces, so the forum would not execute them....

I learned the following trick from PascalDragon that is made to order for that situation:

Also, side note, you can use [nobbc][/nobbc] to display tags without the forum interpreting them, e.g. [code][/code], so you don't need to add spaces.

Let me test something.


begin
  for i := 1 to 10 do
  begin
    myvar[i] := i;
  end;
end;

P.S.: No italics :)
So we can also use [nobbc] and [/nobbc] if we doesn't want to use [code] tags and we won't get code interpreted by forum software. :)
« Last Edit: August 03, 2025, 09:14:13 pm by dseligo »

440bx

  • Hero Member
  • *****
  • Posts: 6560
Re: Generics fighting back.
« Reply #12 on: August 03, 2025, 10:06:10 pm »
I hadn't thought about using it that way.  Ingenious :)
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

avk

  • Hero Member
  • *****
  • Posts: 835
Re: Generics fighting back.
« Reply #13 on: August 04, 2025, 02:55:00 pm »
Instead of wrestling with the 'built in' compare, I copied a Shell sort example and adapted it to the generated/specialed TList<TMyRecord>;
 This may be a less pure way to do it, but for me it was much easier.
...

If you want to use a regular function as a custom comparator in data structures from Generics.Collections, it must be of the form
Code: Pascal  [Select][+][-]
  1.   function({$IF FPC_FULLVERSION<30204}constref{$ELSE}const{$ENDIF}a, b: T): Integer;
  2.  
For example, in your case it could be
Code: Pascal  [Select][+][-]
  1. function CompareTMyRecord({$IF FPC_FULLVERSION<30204}constref{$ELSE}const{$ENDIF}L, R: TMyRecord): Integer;
  2. begin
  3.   Result := Ord(L.ID > R.ID) - Ord(L.ID < R.ID);
  4. end;
  5.  
To use it to sort a TList instance, just pass this function to the comparer constructor, in your case:
Code: Pascal  [Select][+][-]
  1. ...
  2.   L.Sort(specialize TComparer<TMyRecord>.Construct(@CompareTMyRecord));
  3. ...
  4.  
That's all.

Tomu

  • New Member
  • *
  • Posts: 45
Re: Generics fighting back.
« Reply #14 on: August 04, 2025, 07:48:18 pm »
Thanks avk!

Your example/suggestion worked smoothly.

 

TinyPortal © 2005-2018