Recent

Author Topic: Generic methods inside a non-generic class (Delphi vs Object Pascal) [SOLVED]  (Read 1138 times)

Laurent92

  • Newbie
  • Posts: 5
Hello,

I get this error when I compile the following code in OBJFPC mode:

Code: Text  [Select][+][-]
  1. main2.pas(60,21) Fatal: Syntax error, "<" expected but "." found
  2. Fatal: Compilation aborted

In DELPHI mode everything works though.

Is this behavior normal? I cannot use generic methods of a non-generic class in OBJFPC mode or has this been fixed in later versions of the FPC compiler?



Compiler version and target OS:
Code: Text  [Select][+][-]
  1. Free Pascal Compiler version 3.2.2 [2021/05/16] for aarch64
  2. Copyright (c) 1993-2021 by Florian Klaempfl and others
  3. Target OS: Darwin for AArch64

Code: Pascal  [Select][+][-]
  1. {$MODE OBJFPC}
  2. program main2;
  3.  
  4. uses
  5.   fgl, typinfo;
  6.  
  7. type
  8.   IRepository = interface
  9.   ['{8354082d-9ed9-41f7-86ec-2820cc925cd7}']
  10.   end;
  11.  
  12.  TRepositoryA = class(TInterfacedObject, IRepository)
  13.   public
  14.     function tostring(): AnsiString; override;
  15.   end;
  16.  
  17.   TRepositoryB = class(TInterfacedObject, IRepository)
  18.   public
  19.     function tostring(): AnsiString; override;
  20.   end;
  21.  
  22.   TObjectMap = specialize TFPGMap<ShortString, IRepository>;
  23.  
  24.   TFactory = class(TObject)
  25.   private
  26.     _objects: TObjectMap;
  27.   public
  28.     constructor create;
  29.     generic procedure register<T: class>;
  30.     generic function get<T: class>: T;
  31.   end;
  32.  
  33. constructor TFactory.create;
  34. begin
  35.   _objects := TObjectMap.create;
  36.   inherited create;
  37. end;
  38.  
  39. generic procedure TFactory.register<T>;
  40. var
  41.   type_info: PTypeInfo;
  42.   type_name: ShortString;
  43. begin
  44.   type_info := typeinfo(T);
  45.   type_name := type_info^.name;
  46.   _objects.add(type_name, T.create);
  47. end;
  48.  
  49. generic function TFactory.get<T>: T;
  50. var
  51.   type_info: PTypeInfo;
  52.   type_name: ShortString;
  53. begin
  54.   type_info := typeinfo(T);
  55.   type_name := type_info^.name;
  56.   result := T(_objects[type_name]);
  57. end;
  58.  
  59. function TRepositoryA.tostring(): AnsiString;
  60. begin
  61.   result := 'Repository A';
  62. end;
  63.  
  64. function TRepositoryB.tostring(): AnsiString;
  65. begin
  66.   result := 'Repository B';
  67. end;
  68.  
  69. var
  70.   factory: TFactory;
  71.   repo_a: TRepositoryA;
  72.   repo_b: TRepositoryB;
  73. begin
  74.   factory := TFactory.create;
  75.  
  76.   specialize factory.register<TRepositoryA>;
  77.   specialize factory.register<TRepositoryB>;
  78.  
  79.   repo_a := specialize factory.get<TRepositoryA>;
  80.   repo_b := specialize factory.get<TRepositoryB>;
  81.  
  82.   writeln(repo_a.tostring);
  83.   writeln(repo_b.tostring);
  84. end.

Code: Pascal  [Select][+][-]
  1. {$MODE DELPHI}
  2. program main2;
  3.  
  4. uses
  5.   fgl, typinfo;
  6.  
  7. type
  8.   IRepository = interface
  9.   ['{8354082d-9ed9-41f7-86ec-2820cc925cd7}']
  10.   end;
  11.  
  12.   TRepositoryA = class(TInterfacedObject, IRepository)
  13.   public
  14.     function tostring(): AnsiString; override;
  15.   end;
  16.  
  17.   TRepositoryB = class(TInterfacedObject, IRepository)
  18.   public
  19.     function tostring(): AnsiString; override;
  20.   end;
  21.  
  22.   TObjectMap = TFPGMap<ShortString, IRepository>;
  23.  
  24.   TFactory = class(TObject)
  25.   private
  26.     _objects: TObjectMap;
  27.   public
  28.     constructor create;
  29.     procedure register<T: class>;
  30.     function get<T: class>: T;
  31.   end;
  32.  
  33. constructor TFactory.create;
  34. begin
  35.   _objects := TObjectMap.create;
  36.   inherited create;
  37. end;
  38.  
  39. procedure TFactory.register<T>;
  40. var
  41.   type_info: PTypeInfo;
  42.   type_name: ShortString;
  43. begin
  44.   type_info := typeinfo(T);
  45.   type_name := type_info^.name;
  46.   _objects.add(type_name, T.create);
  47. end;
  48.  
  49. function TFactory.get<T>: T;
  50. var
  51.   type_info: PTypeInfo;
  52.   type_name: ShortString;
  53. begin
  54.   type_info := typeinfo(T);
  55.   type_name := type_info^.name;
  56.   result := _objects[type_name] as T;
  57. end;
  58.  
  59. function TRepositoryA.tostring(): AnsiString;
  60. begin
  61.   result := 'Repository A';
  62. end;
  63.  
  64. function TRepositoryB.tostring(): AnsiString;
  65. begin
  66.   result := 'Repository B';
  67. end;
  68.  
  69.  
  70. var
  71.   factory: TFactory;
  72.   repo_a: TRepositoryA;
  73.   repo_b: TRepositoryB;
  74. begin
  75.   factory := TFactory.create;
  76.  
  77.   factory.register<TRepositoryA>;
  78.   factory.register<TRepositoryB>;
  79.  
  80.   repo_a := factory.get<TRepositoryA>;
  81.   repo_b := factory.get<TRepositoryB>;
  82.  
  83.   writeln(repo_a.tostring);
  84.   writeln(repo_b.tostring);
  85. end.
« Last Edit: January 22, 2025, 05:39:26 pm by Laurent92 »

Khrys

  • Full Member
  • ***
  • Posts: 155
Re: Generic methods inside a non-generic class (Delphi vs Object Pascal)
« Reply #1 on: January 22, 2025, 02:50:01 pm »
In  {$mode objfpc}  the mandatory  specialize  keyword must come directly before the thing it specializes - even for methods that are called on an instance, which IMO looks a bit awkward, but it's how the compiler expects it:

Code: Pascal  [Select][+][-]
  1. factory.specialize register<TRepositoryA>;

cdbc

  • Hero Member
  • *****
  • Posts: 1871
    • http://www.cdbc.dk
Re: Generic methods inside a non-generic class (Delphi vs Object Pascal)
« Reply #2 on: January 22, 2025, 04:25:58 pm »
Hi
@Khrys: Sorry, are you kidding me? That can't be right, looks completely /bonkers/...?!?
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

Laurent92

  • Newbie
  • Posts: 5
Re: Generic methods inside a non-generic class (Delphi vs Object Pascal)
« Reply #3 on: January 22, 2025, 05:39:08 pm »
@Khrys,
Thank you!

@cdbc
Hi
@Khrys: Sorry, are you kidding me? That can't be right, looks completely /bonkers/...?!?
Regards Benny

Yeah, that's what I thought too in the beginning, but it does make sense
when you directly replace "register<TRepositoryA>" in DELPHI mode by "specialize register<TRepositoryA>" you will
end up with "factory.specialize register<TRepositoryA>" as weird as it may look




cdbc

  • Hero Member
  • *****
  • Posts: 1871
    • http://www.cdbc.dk
Hahaha
I guess, I'll have to see it a few times, before *that* sinks in  ;D :D %)
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

Zoran

  • Hero Member
  • *****
  • Posts: 1900
    • http://wiki.lazarus.freepascal.org/User:Zoran
Swan, ZX Spectrum emulator https://github.com/zoran-vucenovic/swan

Khrys

  • Full Member
  • ***
  • Posts: 155
This behaviour changed in fpc 3.2. See: https://wiki.freepascal.org/User_Changes_3.2.0#Parsing_of_specialize_has_been_changed

I was initially confused by this response until I found out that this behaviour was introduced in 3.2.0, not removed as I had inferred:

Quote
Parsing of specialize has been changed
  • Old behaviour: specialize was used to initialize a specialization and was followed by a type name that might contain a unit name and parent types
  • New behaviour: specialize is now considered part of the specialized type, just as generic is. This means that unit names and parent types need to be used before the part containing the specialize.
  • Reason: This allows for a more logical usage of specialize in context with nested types (especially if multiple specializations are involved) and more importantly generic functions and methods.
  • Remedy: Put the specialize directly in front of the type which needs to be specialized.

It seems that before 3.2.0, standalone generic functions weren't even possible in FPC (and testing it in Godbolt seems to confirm that suspicion), but please correct me if I'm wrong.

Zoran

  • Hero Member
  • *****
  • Posts: 1900
    • http://wiki.lazarus.freepascal.org/User:Zoran
This behaviour changed in fpc 3.2. See: https://wiki.freepascal.org/User_Changes_3.2.0#Parsing_of_specialize_has_been_changed

I was initially confused by this response until I found out that this behaviour was introduced in 3.2.0, not removed as I had inferred:

I'm sorry if I wasn't quite clear; at least I provided the link to the full explanation.

It seems that before 3.2.0, standalone generic functions weren't even possible in FPC.

Correct. You could easily work around that, though. You could declare a generic class and in that class declare a static function with a parameter of type T.
Swan, ZX Spectrum emulator https://github.com/zoran-vucenovic/swan

PascalDragon

  • Hero Member
  • *****
  • Posts: 5870
  • Compiler Developer
Re: Generic methods inside a non-generic class (Delphi vs Object Pascal)
« Reply #8 on: January 23, 2025, 09:03:58 pm »
@Khrys: Sorry, are you kidding me? That can't be right, looks completely /bonkers/...?!?

The specialize is part of the identifier, like the type parameters are. Take into account functions returning types that again provide generics (e.g. through helper types if they aren't classes or records):

Code: Pascal  [Select][+][-]
  1. SomeClass.specialize SomeFunc<LongInt>(foo, bar).specialize SomeOtherFunc<String>('Foobar')

It seems that before 3.2.0, standalone generic functions weren't even possible in FPC (and testing it in Godbolt seems to confirm that suspicion), but please correct me if I'm wrong.

If you'd look at the New Features 3.2.0 you'd know that yes, generic functions (and methods) were added with 3.2.0 and that was also the main reason why the position of the specialize was changed.

 

TinyPortal © 2005-2018