February 26, 2013

Operator overloading for records


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:

Jeroen Pluimers said...

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/

François Piette said...

Jeroen, your article is outdated. Please update it according to the current version.