Überladen mit operator und generic

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Mathias
Beiträge: 6209
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Überladen mit operator und generic

Beitrag von Mathias »

Ich habe folgendes Beispiel, bei dem ich ein minus für Vectoren überlade,
Die funktioniert auch sehr gut, wen ich von einem Vectoren einen anderen abziehe.
Nun zur Frage, ist es möglich auch ein einzelnes Vorzeichen zu überladen ?
So da die 2 letzten Zeilen funktionieren ?

Code: Alles auswählen

type
  TVector2f = array[0..1] of single;

  function vec2(x, y: single): TVector2f;
  begin
    Result[0] := x;
    Result[1] := y;
  end;

  operator -(const v0, v1: TVector2f) Res: TVector2f; inline;
  begin
    Res[0] := v0[0] - v1[0];
    Res[1] := v0[1] - v1[1];
  end;

var
  v0, v1, v: TVector2f;

begin
  v0 := vec2(1, 2);
  v1 := vec2(4, 5);
  v := v0 - v1;
  v := vec2(0, 0) - v1;  // geht
  v := -v1;              // geht nicht
  v := -vec2(3, 4);      // geht nicht
Zuletzt geändert von Mathias am Mo 11. Sep 2023, 14:57, insgesamt 1-mal geändert.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6209
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Vorzeichen überladen ( operator )

Beitrag von Mathias »

Wen man weis wie, ganz einfach. :oops:

Code: Alles auswählen

  operator -(const v1: TVector2f) Res: TVector2f; inline; overload;
  begin
    Res[0] := -v1[0];
    Res[1] := -v1[1];
  end;
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6209
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Vorzeichen überladen ( operator ) für Vektoren

Beitrag von Mathias »

Echt krass, C++ geht da sogar ein Stück weiter.
Die Vektorgrösse lässt sich dynamisch anpassen.
In Pascal habe ich für jede Überladung für vec2, vec3, vec4 eine extra function schreiben müssen.

https://github.com/ssloy/tinykaboom/blo ... geometry.h

Oder kriegt man dies mit FPC auch irgendwie hin ?
Oder muss es da wirklich passen ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

u-boot
Beiträge: 306
Registriert: Do 9. Apr 2009, 10:10
OS, Lazarus, FPC: Ubuntu 9.10 (L 0.9.28 FPC 2.2.4)
CPU-Target: 32Bit
Wohnort: 785..

Re: Vorzeichen überladen ( operator )

Beitrag von u-boot »

Kommt wohl darauf an wie man Vektor definiert. Für variable Größen hätte ich es mit dynamischen Arrays versucht.
Mathias hat geschrieben:
So 10. Sep 2023, 19:20
Echt krass, C++ geht da sogar ein Stück weiter.
Die Vektorgrösse lässt sich dynamisch anpassen.
In Pascal habe ich für jede Überladung für vec2, vec3, vec4 eine extra function schreiben müssen.

https://github.com/ssloy/tinykaboom/blo ... geometry.h

Oder kriegt man dies mit FPC auch irgendwie hin ?
Oder muss es da wirklich passen ?
Ubuntu 9.10 (L 0.9.28 FPC 2.4.x)

Mathias
Beiträge: 6209
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Vorzeichen überladen ( operator )

Beitrag von Mathias »

Kommt wohl darauf an wie man Vektor definiert. Für variable Größen hätte ich es mit dynamischen Arrays versucht.
Ich habe mich vielleicht schlecht ausgedrückt, eher so halb dynamisch wie in der C++-Version.

Der springend Punkt ist dieser, wo man nur noch sagen muss, wie gross es sein muss.

Code: Alles auswählen

typedef vec<3, float> Vec3f;
In Pascal würde sie in etwa so aussehen.

Code: Alles auswählen

var 
  v2 : TVec<2>
  v3 : TVec<3>
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Warf
Beiträge: 1913
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Vorzeichen überladen ( operator )

Beitrag von Warf »

Geht auch:

Code: Alles auswählen

program Project1;

{$mode objfpc}{$H+}
{$ModeSwitch advancedrecords}

type
  generic TVector<const Dim: SizeInt; T> = record
  public type
    TSelfType = specialize TVector<Dim, T>;
    TFieldArray = array[0..Dim-1] of T;
  private
    function GetData(AIndex: Integer): T; inline;
    procedure SetData(AIndex: Integer; AValue: T); inline;

  public
    Fields: TFieldArray;

    class operator +(const rhs, lhs: TSelfType): TSelfType; inline;
    class operator :=(const AFields: TFieldArray): TSelfType; inline;

    property Data[AIndex: Integer]: T read GetData write SetData; default;
  end;

function TVector.GetData(AIndex: Integer): T;
begin
  Result := Fields[AIndex];
end;

procedure TVector.SetData(AIndex: Integer; AValue: T);
begin
  Fields[AIndex] := AValue;
end;

class operator TVector.+(const rhs, lhs: TSelfType): TSelfType;
var
  i: Integer;
begin
  for i:=0 to Dim-1 do
    Result.Fields[i] := rhs.Fields[i] + lhs.Fields[i];
end;

class operator TVector.:=(const AFields: TFieldArray): TSelfType;
var
  i: Integer;
begin
  for i:=0 to Dim-1 do
    Result.Fields[i] := AFields[i];
end;

type
    TVec3f = specialize TVector<3, Double>;

var
  x, y, z: TVec3f;
begin
  x := [3.5,  2.1, 4.4];
  y := [1.6, -3.2, 0];
  z := x + y;
  WriteLn('Result: (', z[0], ', ', z[1], ', ', z[2], ')');
end.
Allerdings ist das erst in Trunk drin, und noch nicht im Stable FPC.

Mathias
Beiträge: 6209
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Vorzeichen überladen ( operator )

Beitrag von Mathias »

Danke, echt cool was da geht. Ein echt leistungsstarkes Werkzeug.

Ich habe mal folgendes probiert, einen Integer zu verwenden.

Code: Alles auswählen

  class operator TVector. / (const rhs, lhs: TSelfType): TSelfType;
  var
    i: integer;
  begin
    for i := 0 to Dim - 1 do begin
      Result.Fields[i] := rhs.Fields[i] / lhs.Fields[i]; // Fehler
    end;
  end; 
  ....  
  type
  Tvector2f = specialize TVector<2, single>;
  Tvector2i = specialize TVector<2, integer>;   
  
var
  vi0, vi1: Tvector2i;
begin
  vi0 := [1, 2];
  vi1 := vi0 / vi0;    
Wie erwartet bleibt er mit einem "/ " bei Integer stecken.
Kann man dies irgendwie umgehen ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Warf
Beiträge: 1913
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Vorzeichen überladen ( operator )

Beitrag von Warf »

Das geht mit einem anderen Trick, Überladene Funktionen:

Code: Alles auswählen

 // Integer überladung
function Divide(const a, b: Integer): Integer;overload;
begin
  Result := A div B;
end;  

// Float überladung
function Divide(const a, b: Double): Double;overload;
begin
  Result := a/b;
end;

class operator TVector./(const lhs, rhs: TSelfType): TSelfType;
var
  i: Integer;
begin
  for i:=0 to Dim-1 do
    Result.Fields[i] := Divide(lhs.Fields[i], rhs.Fields[i]);
end;
Jetzt sucht der Compiler beim Divide das aus was am besten zu dem typen passt, bei Integer typen die Integer variante mit div, bei float typen die double variante mit /.

Mit einem weiteren neuen Feature aus Trunk kann man das sogar noch weiter generalisieren:

Code: Alles auswählen

// Ganz oben:
{$ModeSwitch implicitfunctionspecialization} 
// ,,,

//  Spezialüberladung für Integer
function Divide(const a, b: Integer): Integer;overload;
begin
  Result := A div B;
end;  

// Generische Überladung für alle anderen typen
generic function Divide<T>(const a, b: T): T;overload;
begin
  Result := a/b;
end;

class operator TVector./(const lhs, rhs: TSelfType): TSelfType;
var
  i: Integer;
begin
  for i:=0 to Dim-1 do
    Result.Fields[i] := Divide(lhs.Fields[i], rhs.Fields[i]);
end; 
Jetzt läuft das so ab, das der Compiler nachschaut ob es für den typen einen spezialfall für Dividie gibt (also hier nur für Integer), und für alle anderen typen wird dann stattdessen die Generische Variante verwendet.

Damit funktioniert das jetzt mit allen typen die / überladen, plus integer. Du könntest jetzt z.B. Vectoren von Vectoren bauen, da die ja / überladen wird dann der korrekte Divide aufruf genutzt (der intern dann den Divide von dem untertypen aufruft)

Mathias
Beiträge: 6209
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Überladen mit operator und generic

Beitrag von Mathias »

Das geht mit einem anderen Trick, Überladene Funktionen:
Danke, auf die Idee hätte man ja selbst kommen können, :oops:
Mit einem weiteren neuen Feature aus Trunk kann man das sogar noch weiter generalisieren:
Dies hat mich gerade auf eine Idee gebracht und habe folgendes ohne Erfolg probiert.
Die procedure wird anstandslos kompiliert, aber weiter unten nicht mehr.

Code: Alles auswählen

generic procedure SwapVar<T>(var a, b: T);
var
  c: T;
begin
  c := a;
  a := b;
  b := c;
end;

var
  a: Single = 2;
  b: Single = 4;
begin
  swapvar(a,b); // error
  swapvar<single>(a,b);  // error 
Und dies scheint auch nicht zu klappen.
Oder kann man dies auch einfach überlisten ?

Code: Alles auswählen

  function min(a, b: TVector);
  begin
     // for 0 to dim-1 do ........
  end;
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Warf
Beiträge: 1913
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Überladen mit operator und generic

Beitrag von Warf »

Du musst entweder die Funktion expliziet spezialisieren:

Code: Alles auswählen

// Mode objFpc:
specialize SwapVar<Single>(a, b);
// Mode Delphi
SwapVar<Single>(a, b);
Im neuen Trunk ist allerdings schon der Modeswitch implicitfunctionspecialization drin, mit dem der FPC die typ Spezialisierung versucht selbst zu ermitteln (z.T. mit mäßigem erfolg):

Code: Alles auswählen

{$Mode ObjFPC}{$H+}
{$ModeSwitch implicitfunctionspecialization}

generic procedure SwapVar<T>(var a, b: T);
var
  c: T;
begin
  c := a;
  a := b;
  b := c;
end;

var
  a: Single = 2;
  b: Single = 4;
begin
  swapvar(a,b); // Geht jetzt
Und diese Implizite Spezialisierung erlaubt wie in meinem letzten beitrag gezeigt, generische funktionen zu haben und die mit Spezialfallfunktionen zu erweitern.
Das könnte man z.B. verwenden für Optimierungszwecke, sodass man z.B. für bestimmte typen wie Floats eigene ASM implementierungen schreibt (die dann z.B. YMM register benutzen), sodass der Compiler dann je nach typ entweder die allgemeine aber potentiell langsame, oder die spezialisierte effiziente funktion aufrufen kann.

Mathias
Beiträge: 6209
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Überladen mit operator und generic

Beitrag von Mathias »

Im neuen Trunk ist allerdings schon der Modeswitch implicitfunctionspecialization drin, mit dem der FPC die typ Spezialisierung versucht selbst zu ermitteln (z.T. mit mäßigem erfolg):
Jetzt geht dies endlich, was ich seit GWBASIC-Zeiten vermisse. :idea:
https://hwiegman.home.xs4all.nl/gw-man/SWAP.html

Und wie sieht es mit dem aus, gibt es da auch etwas ?

Code: Alles auswählen

 function min(a, b: TVector): TVector;
  begin
     // for 0 to dim-1 do ........
  end;
Auf jeden Fall könnte die ganze generic-Sache recht spannend werden.
Ich denke, damit könnte man schon ein ganzen Tutorial aufüllen.
Und diese Implizite Spezialisierung erlaubt wie in meinem letzten beitrag gezeigt, generische funktionen zu haben und die mit Spezialfallfunktionen zu erweitern.
Das könnte man z.B. verwenden für Optimierungszwecke, sodass man z.B. für bestimmte typen wie Floats eigene ASM implementierungen schreibt (die dann z.B. YMM register benutzen), sodass der Compiler dann je nach typ entweder die allgemeine aber potentiell langsame, oder die spezialisierte effiziente funktion aufrufen kann.
Das mit den YMM Register werde ich sicher mal genauer angucken, mit denen kann man bei Vektoren noch recht Dampf machen.
Vor längerer Zeit hatte ich mich mal damit beschäftigt. So wie ich mich erinnern mag, wurde dazumal erst in der Trunc einige dieser Erweiterungen unterstützt.
https://wiki.delphigl.com/index.php/Mat ... iplikation


Bei den geneics sieht man noch gut, das sie in Entwicklung sind , [Ctrl+Space] will nicht mehr richtig. Aber ich denke dies wird sicher auch noch kommen.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Warf
Beiträge: 1913
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Überladen mit operator und generic

Beitrag von Warf »

Mathias hat geschrieben:
Mo 11. Sep 2023, 16:16
Und wie sieht es mit dem aus, gibt es da auch etwas ?

Code: Alles auswählen

 function min(a, b: TVector): TVector;
  begin
     // for 0 to dim-1 do ........
  end;
Hierfür musst du praktisch generics "weiterreichen":

Code: Alles auswählen

generic function min<const dim: sizeint; T>(const a,b: specialize TVector<dim, t>): specialize TVector<dim, t>;
Das wird aber ziemlich schnell ziemlich hässlich. Ein bisschen einfacher ist die generische implementation vom typen zu benutzen mit einem Member:

Code: Alles auswählen

  generic TVector<const Dim: SizeInt; T> = record
  public type
    TSelfType = specialize TVector<Dim, T>;
    TFieldArray = array[0..Dim-1] of T;
  private
    [...]

  public
    [...]
    class operator<(const lhs, rhs: TSelfType): Boolean;
    class function min(elems: Array of TSelfType): TSelfType;
  end;
[...]

class operator TVector.<(const lhs, rhs: TSelfType): Boolean;
begin
  // Implementieren von kleiner relation
end;

class function TVector.min(elems: Array of TSelfType): TSelfType;
var
  elem: TSelfType;
begin
  if Length(elems)=0 then
    // Error
  Result := elems[0];
  for elem in elems do
    if elem < Result then
      Result := Elem;
end;

// benutzung:
minVec := TVec3F.Min([a, b]);
Du kannst natürlich auch eine ganz generische min funktion überladen die mit jedem datentype der < hat funktioniert:

Code: Alles auswählen

generic function min<T>(const elems: Array of T): T;
var
  elem: T;
begin
  if Length(elems)=0 then
    // Error
  Result := elems[0];
  for elem in elems do
    if elem < Result then
      Result := Elem;
end;

// Nutzung mit implicit specialization
  minVec := Min([a, b]);
// Explizite specialization
  minVec := specialize Min<TVec3F>([a, b]);
Ich glaube so eine funktion gibt es sogar schon (in der math unit oder so).

Warf
Beiträge: 1913
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Überladen mit operator und generic

Beitrag von Warf »

Mathias hat geschrieben:
Mo 11. Sep 2023, 16:16
Jetzt geht dies endlich, was ich seit GWBASIC-Zeiten vermisse. :idea:
https://hwiegman.home.xs4all.nl/gw-man/SWAP.html
Wenn du lust hast kannst du den swap auch noch optimieren:

Code: Alles auswählen

// Generisch für alle datentypen
generic procedure Swap<T>(var a, b: T);
var
  tmp: T;
begin
  tmp:=a;
  a:=b;
  b:=tmp;
end;

// Optimierter inline Xor Swap für Integer
procedure Swap(var a, b: Integer); inline;
begin
  a := a Xor b;
  b := b Xor a;
  a := a Xor b;
end;
Die neue Implizite specialisation ist damit extrem mächtig, da es erlaubt ganz spezielle implementierungen zu bauen, die bestimmte eigenschaften von typen ausnutzen, aber falls diese nicht vorhanden sind einfach einen fallback auf eine generische prozedur zu erlauben

Mathias
Beiträge: 6209
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Überladen mit operator und generic

Beitrag von Mathias »

Wenn du lust hast kannst du den swap auch noch optimieren:
Ich habe es folgendermassen mal probiert.
Scheint zu klappen. Auch der String geht durch.

Code: Alles auswählen

  {$ModeSwitch implicitfunctionspecialization}

  generic procedure Swap<T>(var a, b: T);
  var
    pa: PtrUInt absolute a;
    pb: PtrUInt absolute b;
  begin
    pa := pa xor pb;
    pb := pb xor pa;
    pa := pa xor pb;
  end;

var
  b1: byte = 12;
  b2: byte = 45;
  f1: single = 123;
  f2: single = 456;
  s1: string = 'World !';
  s2: string = 'Hello ';

begin
  Swap(b1, b2);
  WriteLn(b1, '  ', b2);

  Swap(f1, f2);
  WriteLn(f1, '  ', f2);

  Swap(s1, s2);
  WriteLn(s1, s2);
end.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten