[fpc-pascal] Generic type conflicts
Sven Barth
pascaldragon at googlemail.com
Fri Nov 8 22:00:46 CET 2019
Am 02.11.2019 um 15:55 schrieb Ryan Joseph via fpc-pascal:
> I've wanted to make a generic version of a vector for a while but I always give up because it seems not very possible. It's probably not even a great idea because many methods don't translate between float and integer but I wanted to prevent other code duplication if possible.
>
> Here's an example of how things break down. Are there any solutions for this currently? I feel like generics need to support some compiler directives so different blocks of code can specialize different depending on the type.
>
> {$mode objfpc}
> {$modeswitch advancedrecords}
>
> program generic_vector_2;
> uses
> Math;
>
> type
> generic TVec2<TScalar> = record
> x, y: TScalar;
> function Normalize: TVec2;
> end;
> TVec2f = specialize TVec2<Float>;
> TVec2i = specialize TVec2<Integer>;
>
> function TVec2.Normalize: TVec2;
> var
> fac: TScalar;
> begin
> // Can't determine which overloaded function to call
> // Incompatible types: got "Extended" expected "LongInt"
> fac:=Sqrt(Sqr(x) + Sqr(y));
> if fac<>0.0 then begin
> // Incompatible types: got "Single" expected "LongInt"
> fac:=1.0/fac;
> result.x:=x*fac;
> result.y:=y*fac;
> end else begin
> result.x:=0;
> result.y:=0;
> end;
> end;
>
> begin
> end.
First of Sqrt always returns a ValReal (aka the best precision floating
point), thus you should declare fac as ValReal. Thus you should cast x
and y to ValReal before passing them to Sqr to avoid overload troubles.
Then you only need to ensure that the adjusted vector components are
passed correctly to the result which will lead to code like this:
=== code begin ===
program tgenvec;
{$mode objfpc}
{$modeswitch advancedrecords}
uses
Math;
type
generic TVec2<TScalar> = record
private type
PScalar = ^TScalar;
public
x, y: TScalar;
function Normalize: TVec2;
end;
function TVec2.Normalize: TVec2;
var
fac, tmpx, tmpy: ValReal;
begin
fac := Sqrt(Sqr(ValReal(x)) + Sqr(ValReal(y)));
if fac <> 0.0 then begin
fac := 1.0 / fac;
tmpx := x * fac;
tmpy := y * fac;
if GetTypeKind(TScalar) in [tkInteger, tkInt64, tkQWord] then begin
Result.x := Round(tmpx);
Result.y := Round(tmpy);
end else if GetTypeKind(TScalar) = tkFloat then begin
Result.x := PScalar(@tmpx)^;
Result.y := PScalar(@tmpy)^;
end;
end else
Result := Default(TVec2);
end;
type
TVec2f = specialize TVec2<Double>;
TVec2i = specialize TVec2<LongInt>;
var
vf: TVec2f;
vi: TVec2i;
begin
vf.x := 2.5;
vf.y := 3.5;
vf := vf.Normalize;
Writeln(vf.x, ' ', vf.y);
// on Win64 this prints 5.8123819371909646E-001 8.1373347120673500E-001
vi.x := 2;
vi.y := 4;
vi := vi.Normalize;
Writeln(vi.x, ' ', vi.y);
// on Win64 this prints 0 1
end.
=== code end ===
The path not taken for the GetTypeKind inside the TVec<>.Normalize will
be optimized away. The pointer conversion is needed, because a floating
point type can not be assigned to an integer type. Sadly the compiler
does not realize that it does not really need to take the address there,
so that will stay even in O4 for the non-floating point case. But hey,
you've got a generic Normalize then...
Regards,
Sven
More information about the fpc-pascal
mailing list