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