Generics und overload

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
MitjaStachowiak
Lazarusforum e. V.
Beiträge: 395
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Generics und overload

Beitrag von MitjaStachowiak »

Hallo,

ich schreibe gerade an einer Unit, die mit Polynomen rechnen kann. Weil ich mich nicht auf eine Genauigkeit festlegen wollte, habe ich einen generischen Typ verwendet, der dann später, je nach Anwendung, durch Single, Double oder Extendet ersetzt werden soll.

Aber Zum Beispiel FloatToStr kann nicht auf den generischen Typ angewendet werden, ich bekomme den Fehler "Error: Can't determine which overloaded function to call".

Man beachte folgenden Code:

Code: Alles auswählen

 
unit PolynomUtil;
 
{$mode objfpc}{$H+}
 
{$modeswitch advancedrecords}
 
interface
 
uses
  Classes, SysUtils, StrTransformErr;
 
type
  generic Polynom<Precision> = class
   public
    type TPolynom = Array of Precision;
    class function ToString(const P : TPolynom) : String; overload;
  end;
  generic TPolynom<Precision> = Array of Precision;
 
implementation
 
class function Polynom.ToString(const P :TPolynom) : String;
var i : integer;
begin
 Result := '';
 i := Length(P)-1;
 if (i = -1) then exit;
 if (P[i] >= 0) then begin
  Result := FloatToStr(P[i]) + ' X^' + IntToStr(i);
  i := i-1;
 end;
 for i := i downto 0 do if (P[i] < 0) then Result := Result + ' - ' + FloatToStr(-P[i]) + ' X^' + IntToStr(i)
 else Result := Result + ' + ' + FloatToStr(P[i]) + ' X^' + IntToStr(i);
end;
 
end.  
 
Aber gerade weil ich nicht meinen Code 3X kopieren will, für Single, Double und Extended, verwende ich den Generic ja.

Bestimmt gibt es dafür eine Lösung. Wer weiß Rat?

Socke
Lazarusforum e. V.
Beiträge: 3178
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Generics und overload

Beitrag von Socke »

MitjaStachowiak hat geschrieben:Bestimmt gibt es dafür eine Lösung. Wer weiß Rat?
Ich denke das ist ein Fehler im Compiler, bitte im Bugtracker eintragen, falls noch nicht vorhanden.

Das Problem lässt sich aber umgehen. Hier ein Quelltextauszug.

Code: Alles auswählen

uses typinfo;
 
class function Polynom.ToString(const P :TPolynom) : String;
var
  i : integer;
  ti: PTypeInfo;
  td: PTypeData;
begin
  // ...
  ti := TypeInfo(Precision);
  // Überprüfen, ob überhaupt ein Gleitkommatyp verwendet wurde
  // lässt sich leider nicht zur Compile-Zeit abfangen
  if ti^.Kind <> tkFloat then
    raise Exception.Create('Nur Gleitkommatypen erlaubt.');
 
  // genauen Float-Typ ermitteln
  td := GetTypeData(ti);
 
  if (P[i] >= 0) then begin
   case td^.FloatType of
     // den Wert zum gewünschten Typ casten.
     ftSingle  : Result := FloatToStr(Single  (P[i])) + ' X^' + IntToStr(i);
     ftDouble  : Result := FloatToStr(Double  (P[i])) + ' X^' + IntToStr(i);
     ftExtended: Result := FloatToStr(Extended(P[i])) + ' X^' + IntToStr(i);
   end;
 
   i := i-1;
  end;  
  // ... 
Nicht ganz elegent, wird aber kompiliert.
Zuletzt geändert von Socke am Do 14. Mai 2015, 20:50, insgesamt 1-mal geändert.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

MitjaStachowiak
Lazarusforum e. V.
Beiträge: 395
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Re: Generics und overload

Beitrag von MitjaStachowiak »

Danke, funktioniert. Man muss noch uses typinfo schreiben. Meine Lösung sieht jetzt so aus:

Code: Alles auswählen

unit PolynomUtil;
 
{$mode objfpc}{$H+}
 
 
interface
 
uses
  Classes, SysUtils, StrTransformErr, typinfo;
 
type
  generic Polynom<Precision> = class
   private
    class function FloatToStr(f : Precision) : String;
   public
    type TPolynom = Array of Precision;
    class function ToString(const P : TPolynom) : String; overload;
  end;
  generic TPolynom<Precision> = Array of Precision;
 
implementation
 
class function Polynom.FloatToStr(f : Precision) : String;
begin
 Result := '?';
 case (PTypeInfo(TypeInfo(Precision))^.Kind) of
  tkFloat : begin
    case (GetTypeData(PTypeInfo(TypeInfo(Precision)))^.FloatType) of
     ftSingle   : Result := SysUtils.FloatToStr(Single(f));
     ftDouble   : Result := SysUtils.FloatToStr(Double(f));
     ftExtended : Result := SysUtils.FloatToStr(Extended(f));
    end;
  end;
 end;
end;
 
class function Polynom.ToString(const P :TPolynom) : String;
var i : integer;
begin
 Result := '';
 i := Length(P)-1;
 if (i = -1) then exit;
 if (P[i] >= 0) then begin
  Result := FloatToStr(P[i]) + ' X^' + IntToStr(i);
  i := i-1;
 end;
 for i := i downto 0 do if (P[i] < 0) then Result := Result + ' - ' + FloatToStr(-P[i]) + ' X^' + IntToStr(i)
 else Result := Result + ' + ' + FloatToStr(P[i]) + ' X^' + IntToStr(i);
end;
 
end.
 
 
So muss ich nicht immer daran denken ;-)
FloatToStr funktioniert ja manchmal auch nicht richtig und sollte durch andere Funktionen ersetzt werden (http://www.lazarusforum.de/viewtopic.php?f=18&t=6409)

Socke
Lazarusforum e. V.
Beiträge: 3178
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Generics und overload

Beitrag von Socke »

MitjaStachowiak hat geschrieben:Danke, funktioniert. Man muss noch uses typinfo schreiben.
Stimmt, hatte ich beim Kopieren vergessen.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

MitjaStachowiak
Lazarusforum e. V.
Beiträge: 395
Registriert: Sa 15. Mai 2010, 13:46
CPU-Target: 64 bit
Kontaktdaten:

Re: Generics und overload

Beitrag von MitjaStachowiak »

Es sei gesagt, dass ich jetzt so ein Preview-Release von FreePascal (Version 3.0.1) verwende, weil die haben jetzt die Unterstützung für Generics stark verbessert. Jetzt sind so coole Sachen, wie:

Code: Alles auswählen

type
 generic Polynom<Precision> = class
  type
   TPolynom = Array of Precision;
 
   TInterpolatePoint = record
    x : Precision;
    y : Precision;
    derivation : Cardinal;
   end;
 
   TMatrix = object
    private
     Mem  : Array of Precision;
     dim1 : Cardinal;
     dim2 : Cardinal;
     function _get(i, j : Cardinal) : Precision; inline;
     procedure _set(i, j : Cardinal; v : Precision); inline;
    public
     procedure SetSize(Zeilen, Spalten : Cardinal);
     property Spalten : Cardinal read dim2;
     property Zeilen : Cardinal read dim1;
     property Objects[i, j : Cardinal] : Precision read _get write _set; default;
   end;
 
   TInterpolateInfo = Array of TInterpolatePoint;
 
  private
   class function FloatToStr(f : Precision) : String;
 
  public
   class function ToString(const P : TPolynom) : String; overload;
 end;
möglich. Das ist deswegen elegant, weil ich beim Aufrufen der Funktionen nur ein mal global das <Precision> spezialisieren muss, und kann dann wie gewohnt programmieren:

Code: Alles auswählen

 
 type PolyEx = specialize Polynom<Extended>; 
 
[...]
 
procedure TForm1.Test(Sender: TObject);
var
  p : PolyEx.TPolynom;
  m : PolyEx.TMatrix;
begin
  SetLength(p, 3);
  p[0] := 1;
  p[1] := 3;
  p[2] := 2;
  ShowMessage(PolyEx.ToString(p));
  m.SetSize(4, 3);
  m[3,2] := 5.32;
  ShowMessage(FloatToStr(m[3,2]));
end;
 
Bei FPC 3.6 kommt bei verschachtelten Klassen mit Generics immer irgend ein Fehler.

[Edit:] Noch 'ne Frage: Wenn ich schreibe

Code: Alles auswählen

   TMatrix = object
     Mem  : Array of Precision;
 
, dann ist Mem ja nur ein Pointer auf einen außerhalb liegenden Speicher, in dem das Array liegt. Wird die Information über die Größe des Arrays dann direkt hinter dem Pointer gespeichert, oder am Anfang des externen Speichers? Und muss man sich bei so verschachtelten Strukturen irgendwie um das Freigeben des Array-Speichers kümmern?
Wenn ich manuell mit dynamischen Variablen und GetMem arbeite, muss ich das ja...

Antworten