Recent

Author Topic: How to typecast generics specialized by related classes  (Read 2334 times)

mm7

  • Full Member
  • ***
  • Posts: 193
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
How to typecast generics specialized by related classes
« on: May 29, 2020, 10:28:27 pm »
Note - FPC 3.2 !!!

Issue: Two generics classes specialized by related classes are not related.
Without option -CR "Verify method calls" FPC gives warning. With -CR it gives error.

How to make the specialized generics TGenClassA and TGenClassB related?
like it would be something like
type TGenClassB = class(TGenClassA) specialize TGenClass<TClassB>

Code: Pascal  [Select][+][-]
  1. program Project1;
  2. {$mode objfpc}{$H+}
  3.  
  4. type
  5.   TClassA = class
  6.   public
  7.     id: integer;
  8.   end;
  9.  
  10.   TClassB = class(TClassA)
  11.   public
  12.     name: String;
  13.   end;
  14.  
  15.   generic TGenClass<_GT> = class
  16.     class function maxId(a,b: _GT):_GT;
  17.   end;
  18.  
  19.   class function TGenClass.maxId(a,b: _GT):_GT;
  20.   begin
  21.     if a.Id > b.Id then
  22.       result := a
  23.     else
  24.       result := b;
  25.   end;
  26.  
  27. type
  28.   TGenClassA = specialize TGenClass<TClassA>;
  29.   TGenClassB = specialize TGenClass<TClassB>;
  30.  
  31. procedure proc(v:TGenClassA);
  32. begin
  33.  // some processing
  34. end;
  35.  
  36. var
  37.   A:TClassA;
  38.   B:TClassB;
  39.   GA:TGenClassA;
  40.   GB:TGenClassB;
  41. begin
  42.   A:=TClassA.Create;
  43.   B:=TClassB.Create;
  44.   GA:=TGenClassA.Create;
  45.   GB:=TGenClassB.Create;
  46.  
  47.   proc(TGenClassA(GB)); // whith fpc -CR it gives Error: Class or Object types "TGenClass<Project1.TClassB>" and "TGenClass<Project1.TClassA>" are not related
  48.   //proc(GB as TGenClassA); // Error: Class or Object types "TGenClass<Project1.TClassB>" and "TGenClass<Project1.TClassA>" are not related
  49. end.
  50.  

ASerge

  • Hero Member
  • *****
  • Posts: 2223
Re: How to typecast generics specialized by related classes
« Reply #1 on: May 30, 2020, 09:36:25 am »
Forget about generic and use either the usual class hierarchy or interfaces.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: How to typecast generics specialized by related classes
« Reply #2 on: May 30, 2020, 10:51:25 am »
How to make the specialized generics TGenClassA and TGenClassB related?
like it would be something like
type TGenClassB = class(TGenClassA) specialize TGenClass<TClassB>

You can't make them related, because they are not related. A TGenClass<TClassA> can contain other descendants of TClassA which could lead to problems with accessing them as a TGenClass<TClassB>. And even the other way round (as you have it) can lead to problems, for example if non-virtual methods are called inside the generic.

As ASerge said it's better to construct a nice class hierarchy or work with interfaces if you need something specific.

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: How to typecast generics specialized by related classes
« Reply #3 on: May 30, 2020, 10:53:09 am »
Or make proc() a member of TGenClass?
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2.  
  3. type
  4.   TClassA = class
  5.   public
  6.     id: integer;
  7.   end;
  8.  
  9.   TClassB = class(TClassA)
  10.   public
  11.     name: String;
  12.   end;
  13.  
  14.   generic TGenClass<_GT:TClassA> = class
  15.     class function maxId(a,b: _GT):_GT;
  16.   public
  17.     procedure proc(v:TGenClass);
  18.   end;
  19.  
  20.   class function TGenClass.maxId(a,b: _GT):_GT;
  21.   begin
  22.     if a.Id > b.Id then
  23.       result := a
  24.     else
  25.       result := b;
  26.   end;
  27.  
  28. type
  29.   TGenClassA = specialize TGenClass<TClassA>;
  30.   TGenClassB = specialize TGenClass<TClassB>;
  31.  
  32. generic procedure TgenClass.proc(v:TGenClass);
  33. begin
  34.  // some processing
  35. end;
  36.  
  37. var
  38.   A:TClassA;
  39.   B:TClassB;
  40.   GA:TGenClassA;
  41.   GB:TGenClassB;
  42. begin
  43.   A:=TClassA.Create;
  44.   B:=TClassB.Create;
  45.   GA:=TGenClassA.Create;
  46.   GB:=TGenClassB.Create;
  47.   GB.proc(GB);
  48. end.

Now proc is part of both A and B.
 
Specialize a type, not a var.

mm7

  • Full Member
  • ***
  • Posts: 193
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: How to typecast generics specialized by related classes
« Reply #4 on: May 30, 2020, 05:01:16 pm »
Quote
Or make proc() a member of TGenClass?

that how it was in my code for FPC3.0. In FPC 3.2 it is not allowed.

project1.lpr(31,27) Error: Generics without specialization cannot be used as a type for a variable

Any way to force the typecast?

Quote
construct a nice class hierarchy or work with interfaces if you need something specific.

The history of this generic is following.
There was type TFastList, that was kind of TFPList. It was not generic.
In program it was used to store pointers of all kinds of objects. It can be sorted and fast searchable. Internally it converts object pointer to PtrUInt to sort.

I've made it generic and specialized it for the classes to organize its usage and improve safety.
However in some places there are procedures that previously accepted TFastList parameter and can work with related classes like TClassA and TClassB=class(TClassA).

Quote
A TGenClass<TClassA> can contain other descendants of TClassA which could lead to problems with accessing them as a TGenClass<TClassB>. And even the other way round (as you have it) can lead to problems, for example if non-virtual methods are called inside the generic.

Here I do not understand. If in other places of program I can define a variable V:TClassA and then assign TClassB instance to it and use it, why a generic specialized to TClassA cannot do it? The var V is also sort of "specialized" to TClassA, but it can do it.

In reality my class TFastList is type agnostic. It is just sorted pointers list. And it does not even call any methods of the objects.
I know the example is not like this. But anyway.
What would be a workaround?
 

mm7

  • Full Member
  • ***
  • Posts: 193
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: How to typecast generics specialized by related classes
« Reply #5 on: May 30, 2020, 05:54:48 pm »
Found solution from "furious programming" https://forum.lazarus.freepascal.org/index.php?topic=35997.0
last in the thread

it works!

basically it needs const and absolute
Code: Pascal  [Select][+][-]
  1. procedure TgenClass.proc(const aV);
  2. var V:TGenClassA absolute aV;
  3. begin
  4. ...
  5. end;
  6.  
« Last Edit: May 30, 2020, 06:04:17 pm by mm7 »

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: How to typecast generics specialized by related classes
« Reply #6 on: May 30, 2020, 06:13:13 pm »
that how it was in my code for FPC3.0. In FPC 3.2 it is not allowed.

project1.lpr(31,27) Error: Generics without specialization cannot be used as a type for a variable
No not the typecast , but my code works (very recent trunk, as usual)
And your own full code with some slight mods.....

It is a bit strange that you have issues with 3.2.0, since it was either backported from trunk or was already there.
« Last Edit: May 30, 2020, 06:15:48 pm by Thaddy »
Specialize a type, not a var.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: How to typecast generics specialized by related classes
« Reply #7 on: May 30, 2020, 06:28:25 pm »
Quote
A TGenClass<TClassA> can contain other descendants of TClassA which could lead to problems with accessing them as a TGenClass<TClassB>. And even the other way round (as you have it) can lead to problems, for example if non-virtual methods are called inside the generic.

Here I do not understand. If in other places of program I can define a variable V:TClassA and then assign TClassB instance to it and use it, why a generic specialized to TClassA cannot do it? The var V is also sort of "specialized" to TClassA, but it can do it.

You can assign TClassB instances to TGenClass<TClassA> without any problems. What you can not do is assign a TGenClass<TClassB> to a TGenClass<TClassA>, because the two are completely different types with different code. Take this for example:

Code: Pascal  [Select][+][-]
  1. program tgentest;
  2.  
  3. {$mode objfpc}
  4.  
  5. type
  6.   TTestA = class
  7.     // not virtual!
  8.     procedure Test;
  9.   end;
  10.  
  11.   TTestB = class(TTestA)
  12.     procedure Test;
  13.   end;
  14.  
  15.   generic TGTest<T: TTestA> = class
  16.     F: T;
  17.     procedure Test;
  18.   end;
  19.  
  20. type
  21.   TGTestA = specialize TGTest<TTestA>;
  22.   TGTestB = specialize TGTest<TTestB>;
  23.  
  24. procedure TTestA.Test;
  25. begin
  26.   Writeln('TTestA');
  27. end;
  28.  
  29. procedure TTestB.Test;
  30. begin
  31.   Writeln('TTestB');
  32. end;
  33.  
  34. procedure TGTest.Test;
  35. begin
  36.   F.Test;
  37. end;
  38.  
  39. var
  40.   a: TTestA;
  41.   b: TTestB;
  42.   ga, ga2: TGTestA;
  43.   gb: TGTestB;
  44.   ga3: TGTestA absolute gb;
  45. begin
  46.   a := TTestA.Create;
  47.   b := TTestB.Create;
  48.   ga := TGTestA.Create;
  49.   ga.F := a;
  50.   gb := TGTestB.Create;
  51.   ga.F := b;
  52.  
  53.   // will write TTestA
  54.   ga.Test;
  55.   // will write TTestB
  56.   gb.Test;
  57.  
  58.   ga2 := TGTestA(gb);
  59.   // will write TTestA despite gb being a TGTestB!
  60.   ga2.Test;
  61.  
  62.   // will write TTestA despite gb being a TGTestB!
  63.   ga3.Test;
  64. end.

The compiler is free to optimize TGenClass<TClassA> differently from TGenClass<TClassB>, because TClassA might provide different optimization oppertunities than TClassB (e.g. inlining, etc.). As such casting two unrelated generic classes (or using absolute) can break at any time depending on the optimization the compiler version. You're breaking the type system with this!

mm7

  • Full Member
  • ***
  • Posts: 193
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: How to typecast generics specialized by related classes
« Reply #8 on: May 30, 2020, 06:56:07 pm »
Ideally compiler should know, if I specialize generics by related classes these generics should be somewhat related. Not completely unrelated.
It should check generic methods how they use specialized fields/methods/properties etc...

But it is not done this way. Yet. May be it will be in FPC4? :)

So, what would be a solution?

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: How to typecast generics specialized by related classes
« Reply #9 on: May 30, 2020, 07:12:08 pm »
Ideally compiler should know, if I specialize generics by related classes these generics should be somewhat related. Not completely unrelated.
It should check generic methods how they use specialized fields/methods/properties etc...

But it is not done this way. Yet. May be it will be in FPC4? :)

The generics implementation is a complicated beast. I'm happy that it works as much as it does and there are enough other things I need to address first (for example code duplication for the same specialization in different, unrelated units).

So, what would be a solution?

As we already suggested: better think about your class hierarchy. Why do you need to access a list of TClassB as a list of TClassA? Breaking the type system usually means that you have a conceptual problem in your code.

mm7

  • Full Member
  • ***
  • Posts: 193
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: How to typecast generics specialized by related classes
« Reply #10 on: May 31, 2020, 02:38:40 am »
I appreciate your work and understand complexity of generics.

Quote
Why do you need to access a list of TClassB as a list of TClassA? Breaking the type system usually means that you have a conceptual problem in your code.

I disagree. In my case nothing is wrong. It is not breaking the system. It is using OOP polymorphism.
It is completely legitimate to use single var of type TClassA to assign and work with an instance of its descendant class TClassB.
Why lists of vars of TClassA should be different in this regard?




Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: How to typecast generics specialized by related classes
« Reply #11 on: May 31, 2020, 07:17:15 am »
I disagree. In my case nothing is wrong. It is not breaking the system. It is using OOP polymorphism.
It is completely legitimate to use single var of type TClassA to assign and work with an instance of its descendant class TClassB.
Why lists of vars of TClassA should be different in this regard?
Because polymorphism works for objects themselves, but not for object containers. TGenClassB should be directly inherited from TGenClassA. (Doesn't work anyway) Casting one container to another - is what is considered to be hack, i.e. isn't guaranteed to work. Adding TClassB to container for TClassA is ok. Casting TClassB container to TClassA container isn't. Right use of polymorphism is - to use TClassA container and add TClassB objects to it or, if it's not possible, provide some interface, that will cast TClassB to TClassA internally. Another variant - if you don't want to add it to TGenClass, make "proc" generic too, i.e. "procedure proc<T>(AGenClass:TGenClass<T>);". Delphi can do it. Not sure about FPC.
« Last Edit: May 31, 2020, 07:22:54 am by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: How to typecast generics specialized by related classes
« Reply #12 on: May 31, 2020, 10:59:54 am »
I disagree. In my case nothing is wrong. It is not breaking the system. It is using OOP polymorphism.
It is completely legitimate to use single var of type TClassA to assign and work with an instance of its descendant class TClassB.
Why lists of vars of TClassA should be different in this regard?
Because polymorphism works for objects themselves, but not for object containers. TGenClassB should be directly inherited from TGenClassA. (Doesn't work anyway) Casting one container to another - is what is considered to be hack, i.e. isn't guaranteed to work. Adding TClassB to container for TClassA is ok. Casting TClassB container to TClassA container isn't. Right use of polymorphism is - to use TClassA container and add TClassB objects to it or, if it's not possible, provide some interface, that will cast TClassB to TClassA internally.

This. ;D

Another variant - if you don't want to add it to TGenClass, make "proc" generic too, i.e. "procedure proc<T>(AGenClass:TGenClass<T>);". Delphi can do it. Not sure about FPC.

Needs FPC 3.2 or newer (though Delphi does not support global generic functions, only methods)

mm7

  • Full Member
  • ***
  • Posts: 193
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: How to typecast generics specialized by related classes
« Reply #13 on: May 31, 2020, 04:55:24 pm »
Code: Pascal  [Select][+][-]
  1. program ProcGeneric;
  2. {$mode ObjFPC}
  3.  
  4. type
  5.  
  6.   generic TMyList<ItemType> = class
  7.     storage: array of ItemType;
  8.     procedure add(aItem:ItemType);
  9.   end;
  10.  
  11.   procedure TMyList.Add(aItem:ItemType);
  12.   begin
  13.     setLength(storage, length(storage)+1);
  14.     storage[length(storage)-1] := aItem;
  15.   end;
  16.  
  17. type
  18.   TClassA = class end;
  19.   TClassB = class(TClassA) end;
  20.  
  21.   TMyListA = specialize TMyList<TClassA>;
  22.   TMyListB = specialize TMyList<TClassB>;
  23.  
  24.   TListUser = class
  25.     generic procedure UseList<ListType>(aList: ListType);
  26.   end;
  27.  
  28. generic procedure TListUser.UseList<ListType>(aList: ListType);
  29. var b:TClassB;
  30. begin
  31.   b:=TClassB.Create;
  32.   aList.Add(b);
  33. end;
  34.  
  35. generic procedure GlobalUseList<ListType>(aList: ListType);
  36. var b:TClassB;
  37. begin
  38.   b:=TClassB.Create;
  39.   aList.Add(b);
  40. end;
  41.  
  42. var
  43.   MyListA: TMyListA;
  44.   ListUser: TListUser;
  45.  
  46. begin
  47.   MyListA := TMyListA.Create;
  48.   ListUser:= TListUser.Create;
  49.  
  50.   specialize GlobalUseList<TMyListA>(MyListA);
  51.  
  52.   specialize ListUser.UseList<TMyListA>(MyListA);  //Fatal: Syntax error, "<" expected but "." found
  53.  
  54. end.
  55.  

It works for GlobalUseList, but does not work for for class method call.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: How to typecast generics specialized by related classes
« Reply #14 on: May 31, 2020, 05:55:22 pm »
Code: Pascal  [Select][+][-]
  1. program ProcGeneric;
  2. {$mode ObjFPC}
  3.  
  4. type
  5.  
  6.   generic TMyList<ItemType> = class
  7.     storage: array of ItemType;
  8.     procedure add(aItem:ItemType);
  9.   end;
  10.  
  11.   procedure TMyList.Add(aItem:ItemType);
  12.   begin
  13.     setLength(storage, length(storage)+1);
  14.     storage[length(storage)-1] := aItem;
  15.   end;
  16.  
  17. type
  18.   TClassA = class end;
  19.   TClassB = class(TClassA) end;
  20.  
  21.   TMyListA = specialize TMyList<TClassA>;
  22.   TMyListB = specialize TMyList<TClassB>;
  23.  
  24.   TListUser = class
  25.     generic procedure UseList<ListType>(aList: ListType);
  26.   end;
  27.  
  28. generic procedure TListUser.UseList<ListType>(aList: ListType);
  29. var b:TClassB;
  30. begin
  31.   b:=TClassB.Create;
  32.   aList.Add(b);
  33. end;
  34.  
  35. generic procedure GlobalUseList<ListType>(aList: ListType);
  36. var b:TClassB;
  37. begin
  38.   b:=TClassB.Create;
  39.   aList.Add(b);
  40. end;
  41.  
  42. var
  43.   MyListA: TMyListA;
  44.   ListUser: TListUser;
  45.  
  46. begin
  47.   MyListA := TMyListA.Create;
  48.   ListUser:= TListUser.Create;
  49.  
  50.   specialize GlobalUseList<TMyListA>(MyListA);
  51.  
  52.   specialize ListUser.UseList<TMyListA>(MyListA);  //Fatal: Syntax error, "<" expected but "." found
  53.  
  54. end.
  55.  

It works for GlobalUseList, but does not work for for class method call.

The specialize keyword is part of the identifier that is generic, so in this case UseList: ListUser.specialize UseList<TMyListA>(MyListA). The reason for this becomes more clear if more specializations are involved in one statement/expression.

 

TinyPortal © 2005-2018