[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