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.

_______________________________________________
fpc-pascal maillist  -  fpc-pascal@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply via email to