Dear FPC community,
The FPC developers are pleased to announce the implementation of a new feature: implicit generic function specializations. This feature was implemented by Ryan Joseph, so thank you very much, Ryan.
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.
Assume you have the following function:
generic function Add<T>(aArg1, aArg2: T): T;
begin
Result := aArg1 + aArg2;
end;
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);
The compiler will automatically determine the type of the generic parameters based on the parameters you pass in (this is always done left to right). Depending on the passed in parameters (especially if you're using constant values like in the example instead of variables) the compiler might however pick a different type than you expected. You can enforce a specific type by either explicitely specializing the method as before or by inserting a type cast. In the example above the compile will specialize the call with the parameters “
2, 5” using an 8-bit signed type (Pascal prefers signed types) instead of a
LongInt as in the explicit specialization. If you use “
LongInt(2), 5” as parameters then the compiler will pick that instead, however with “
2, LongInt(5)” it will still pick an 8-bit type, because the parameter types are determined left to right.
If there exists a non-generic overload for which the parameters types match exactly, the compiler will pick that instead of specializing something anew. So assume you also have the following function in scope:
function Add(aArg1, aArg2: LongInt): LongInt;
begin
Result := aArg1 + aArg2;
end;
In the case of “
Add(2, 5)” the compiler will
not pick the non-generic function, because it determines that an 8-bit type is enough, however if you use “
Add(LongInt(2), 5)” the compiler will pick the non-generic function.
Aside from simple parameters the compiler also supports arrays and function/method variables:
generic function ArrayFunc<T>(aArg: specialize TArray<T>): T;
var
e: T;
begin
Result := Default(T);
for e in aArg do
Result := Result + e;
end;
type
generic TTest<T> = function(aArg: T): T;
generic function Apply<T>(aFunc: specialize TTest<T>; aArg: T): T;
begin
Result := aFunc(aArg);
end;
function StrFunc(aArg: String): String;
begin
Result := UpCase(aArg);
end;
function NegFunc(aArg: LongInt): LongInt;
begin
Result := - aArg;
end;
begin
Writeln(ArrayFunc([1, 2, 3])); // will write 6
Writeln(ArrayFunc(['Hello', 'FPC', 'World'])); // will write HelloFPCWorld
Writeln(Apply(@StrFunc, 'Foobar')); // will write FOOBAR
Writeln(Apply(@NegFunc, 42)); // will write -42
end.
There are of course a few restrictions for this feature:
- all generic parameters must be used in the declaration of the routine (implementation only type parameters are not allowed)
- all parameters that have a generic type must not be default parameters, they need to be used in the call or their type must have been fixed by a parameter further left (as currently default values for parameters of a generic type are not supported this is not much of a restriction, but should that change (e.g. Default(T)) then this restriction will apply)
- the generic routine must not have constant generic parameters (this might be extended in the future with e.g. static arrays or file types, but for now this restriction stands)
- the result type is not taken into account, so if only the result type of a routine is generic then an implicit specialization does not work either
- function/method pointers to implicit specializations are not yet supported (pointers to explicit specializations are not yet supported either; once this changes the former will change as well)
- the compiler will silently discard generic functions that it can't specialize the declaration of; however if the declaration can be specialized correctly, but for whatever reason the implementation can not then this will trigger a compilation error
This feature is by and in itself Delphi-compatible however there might be differences in what FPC can implicitely specialize and what Delphi can. Especially if Delphi can specialize something that FPC can not, this should be reported.