[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}


   generic TVec2<TScalar> = record
   private type
     PScalar = ^TScalar;
     x, y: TScalar;
     function Normalize: TVec2;

function TVec2.Normalize: TVec2;
   fac, tmpx, tmpy: ValReal;
   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 else
     Result := Default(TVec2);

   TVec2f = specialize TVec2<Double>;
   TVec2i = specialize TVec2<LongInt>;

   vf: TVec2f;
   vi: TVec2i;
   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

=== 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...


More information about the fpc-pascal mailing list