[fpc-pascal] Feature announcement: implicit generic function specializations

Martin Frb lazarus at mfriebe.de
Fri Apr 22 20:51:56 CEST 2022


On 20/04/2022 19:15, Sven Barth via fpc-pascal wrote:
> This feature allows you to use generic routines (functions, 
> procedures, methods) without explicitely specializing them (“<…>” in 
> Delphi modes and “specialize …<…>” in non-Delphi modes) as long as the 
> compiler can determine the correct parameter types for the generic.
>
> This feature is enabled with the modeswitch 
> ImplicitFunctionSpecialization and is for now not enabled by default 
> as this has the potential to break existing code.
>

One more step by Delphi to remove type safety.
IMHO a good option would have been to allow specializing multiple 
overloads in a single statement. But since Embarcadero has decided 
otherwise....

I did explore what happens if I throw different types at it, and see how 
the current implementation deals with this (what I call) lack of type 
safety.
And also asked the question, how should it act?  (Because the current 
behaviour is new, expected to need fixes, and can obviously be fixed).

> Assume you have the following function:
>   generic function Add<T>(aArg1, aArg2: T): T;
> ...
>
> Up to now you could only use this function as follows:
>
> SomeStr := specialize Add<String>('Hello', 'World');
> SomeInt := specialize Add<LongInt>(2, 5);
>
> However with implicit function specializations enabled you can also 
> use it as follows:
>
> SomeStr := Add('Hello', 'World');
> SomeInt := Add(2, 5);

So what happens if:

var
   b: Byte;
   c: Cardinal;
begin
   Add(b, c);

Well, I tested: It uses the type of the first Param. So it calls a 
function for both param of type Byte. The cardinal argument is 
converted. (potentially truncated).

If you use numeric constants:
   writeln('   0', Add(0, 0) );      // ShortInt
   writeln('1000', Add(1000, 1000) );  // SmallInt
   writeln('100K', Add(100000, 100000) );  // Integer

So then, if you try to fix   " Add(b, c)" by checking that b and c have 
the same type => "Add(c, 0)" will fail => because 0 is ShortInt and not 
cardinal.


----------------------------
I created a little test program (see bottom of mail).

And it gives a strange warning:

Compile Project, Target: 
C:\Users\martin\AppData\Local\Temp\project1.exe: Success, Warnings: 1, 
Hints: 3
project1.lpr(67,52) Warning: Range check error while evaluating 
constants (100000 must be between -128 and 127)
project1.lpr(41,18) Hint: Local proc "Add$1" is not used
72 lines compiled, 0.2 sec, 105136 bytes code, 5476 bytes data

Line 41 is the declaration of the generic
    generic function Add<T>(aArg1, aArg2: T): T;

So why does it generate "Add$1" if it does not use it? (Or rather why 
does it warn, if this is some internal details?)

--------------------------------
And if you add to the app (at the top)

function Add(aArg1, aArg2: Int64): Int64; overload;
begin
//  write(' Int64 overload ');
   Result := aArg1 + aArg2;
end;

Then the line  (Shortint, because the first param is ShortInt)
  writeln('#### 1,CK = ',Add(0, 100000) ); // ShortInt

Will no longer call the specialized function, but instead use the int64 
version.

If you comment all "Add(...)" calls, except that one
=> Then you get an additional hint: "project1.lpr(81,41) Hint: Local 
proc "Add$1$crc9AB0BCED" is not used"
So then that very line generates a specialized version for the call, and 
then the compiler does not use it.


Yet if INSTEAD of adding the hardcoded Int64 variant (as given above), I 
let the compile create a Int64 version by adding
     writeln('#### 1,CK = ',Add(int64(0), 100000) ); // ShortInt
before the line above... Well the line above will not use that int64 
version, but use its own specialization...



program Project1;
{$mode objfpc}
{$ModeSwitch ImplicitFunctionSpecialization }
{//$H+}

uses SysUtils;

Procedure Foo(a: Integer; out r: ShortInt);
begin
   write(' ShortInt ');
   r := a;
end;
Procedure Foo(a: Integer; out r: SmallInt);
begin
   write(' SmallInt ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Byte);
begin
   write(' Byte ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Word);
begin
   write(' Word ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Cardinal);
begin
   write(' Cardinal ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Integer);
begin
   write(' Integer ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Int64);
begin
   write(' Int64 ');
   r := a;
end;
Procedure Foo(a: Integer; out r: AnsiString);
begin
   write(' Ansi ');
   r := ' _A:'+IntToStr(a);
end;
Procedure Foo(a: Integer; out r: ShortString);
begin
   write(' Short ');
   r := ' _S:'+IntToStr(a);
end;

generic function Add<T>(aArg1, aArg2: T): T;
var x: T;
begin
   Foo(SizeOf(T), x);
   {$R-}
   Result := aArg1 + aArg2 + x;
end;

var
   b: byte;
   c: cardinal;
begin
   b := 0;
   c := 0;

   writeln('####    B = ',Add(b, b) );
   writeln('####    C = ',Add(c, c) );
   writeln('####    0 = ',Add(0, 0) );  // ShortInt
   writeln('#### 1000 = ',Add(1000, 1000) );  // SmallInt
   writeln('#### 100K = ',Add(100000, 100000) ); // Integer

   writeln;
   writeln('####  c,b = ',Add(c, b) ); // Cardinal
   writeln('####  b,c = ',Add(b, c) ); // Byte
   writeln('#### CK,1 = ',Add(100000, 1) ); // Integer
   {$R-}
   writeln('#### 1,CK = ',Add(0, 100000) ); // ShortInt

   writeln('#### Str  = ',Add('x1', 'x1') );
   readln;
end.



More information about the fpc-pascal mailing list