Operator overloading for records
Operator overloading is a mechanism which gives you control over what an operator does on a given data type. Applied to a record, you can use it to provide a transparent conversion from one data type to another.
The example code I will show in a moment comes from one of my applications (simplified version for brevity). I have a system which requires a parameter representing a value usually expressed as a string but actually at the low level is represented by a byte. The byte value can be in the range 0 to 5 while the usual string representation is like a capacitor value. The string value can only take 6 different values: 0.25pF, 0.5pF, 1pF, 2pF, 4pF and 8pF. (“pF” stands for picofarad where farad is the capacitor measure unit). This could looks strange to you but believe me, the user really likes that strings.
In my application, there are places where I have the string representation and other places where I have the byte representation.
I have used a record to store the value. Basically the declaration is simply:
TData = record Value : Byte; end;
As it is, this declaration is not very interesting. But wait! Once you add operator overloading, you can write this:
var X : TData; N : Integer; begin X := 3; // Assign a byte value Memo1.Lines.Add(X); // Get value as a string X := '0.50pF'; // Assign a string value Inc(X); // Increment whatever it is Memo1.Lines.Add(X); // Get value as a string N := X; // Get value as an integer Memo1.Lines.Add(IntToStr(N)); end;
As you can guess, everything is working as expected. The compiler magically calls the correct function I have defined.
The final declaration of TData is as follow:
TData = record Value : BYTE; class operator Implicit(AValue : String) : TData; class operator Implicit(AValue : TData) : String; class operator Implicit(AValue : BYTE) : TData; class operator Implicit(AValue : TData) : BYTE; class operator Inc(AValue : TData) : TData; end;
The lines with “class operator implicit” redefine the set or get value for the record. I made 4 variations in order to have TData to/from string and integer. Of course, you may add as many data type couples as you need.
The line “class operator Inc” redefines the “Inc” operator.
I have not used it here, but there is a full list of operators you can overload. You can find the list on Embarcadero DocWiki
Complete source code is below. Create a new VCL Form application, drop a TButton and a TMemo on the form and use the code below.
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Vcl.StdCtrls; type TData = record Value : BYTE; class operator Implicit(AValue : String) : TData; class operator Implicit(AValue : TData) : String; class operator Implicit(AValue : BYTE) : TData; class operator Implicit(AValue : TData) : BYTE; class operator Inc(AValue : TData) : TData; end; TForm1 = class(TForm) Memo1: TMemo; Button1: TButton; procedure Button1Click(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} function MyStrToFloat(const S : String) : Extended; var OldSep : Char; begin OldSep := FormatSettings.DecimalSeparator; try // Accepte either '.' or ',' as decimal separator if Pos('.', S) > 0 then FormatSettings.DecimalSeparator := '.' else if Pos(',', S) > 0 then FormatSettings.DecimalSeparator := ','; Result := StrToFloat(S); finally FormatSettings.DecimalSeparator := OldSep; end; end; class operator TData.Implicit(AValue: TData): String; begin case AValue.Value of 0 : Result := '0.25pF'; 1 : Result := '0.50pF'; 2 : Result := '1pF'; 3 : Result := '2pF'; 4 : Result := '4pF'; 5 : Result := '8pF'; else raise ERangeError.Create('Invalid value index "' + IntToStr(AValue.Value) + '"'); end; end; class operator TData.Implicit(AValue: String): TData; var I : Integer; S : String; V : Extended; begin I := Pos('pF', AValue); if I < 1 then S := Trim(AValue) else S := Trim(Copy(AValue, 1, I - 1)); V := MyStrToFloat(S); if V = 0.25 then Result.Value := 0 else if V = 0.50 then Result.Value := 1 else if V = 1.00 then Result.Value := 2 else if V = 2.00 then Result.Value := 3 else if V = 4.00 then Result.Value := 4 else if V = 8.00 then Result.Value := 5 else raise ERangeError.Create('Invalid value string "' + AValue + '"'); end; class operator TData.Implicit(AValue: BYTE): TData; begin Result.Value := AValue; end; class operator TData.Implicit(AValue: TData): BYTE; begin Result := AValue.Value; end; class operator TData.Inc(AValue: TData): TData; begin if AValue.Value >= 5 then raise ERangeError.Create('Cannot increment "' + IntToStr(AValue.Value) + '"'); Result.Value := AValue.Value + 1; end; procedure TForm1.Button1Click(Sender: TObject); var Data : TData; N : Integer; begin Data := 3; Memo1.Lines.Add(Data); Data := '0.25pF'; Memo1.Lines.Add(Data); Data := '0,25pF'; Memo1.Lines.Add(Data); Inc(Data); Memo1.Lines.Add(Data); N := Data; Memo1.Lines.Add(IntToStr(N)); end; end.Follow me on Twitter
Follow me on LinkedIn
Follow me on Google+
Visit my website: http://www.overbyte.be
The article is available at:
http://francois-piette.blogspot.com/2013/02/operator-overloading-for-records.html
2 comments:
Be careful with implicit operators, especially when type coercion can be involved. http://wiert.me/2009/10/19/delphi-operator-overloading-table-of-operators-names-and-some-notes-on-usage-and-glitches/
Jeroen, your article is outdated. Please update it according to the current version.
Post a Comment