Advanced Records, Operator Overloading & Helpers

In FPC objfpc mode, operators can also be declared globally with symbols:

31.1 Operator overloading in records (Delphi syntax)

program Complex;
{$IFDEF FPC}{$mode delphi}{$ENDIF}
{$APPTYPE CONSOLE}
uses SysUtils;

type
  TComplex = record
    Re, Im: Double;
    class function Create(ARe, AIm: Double): TComplex; static;
    class operator Add(const A, B: TComplex): TComplex;
    class operator Subtract(const A, B: TComplex): TComplex;
    class operator Multiply(const A, B: TComplex): TComplex;
    class operator Equal(const A, B: TComplex): Boolean;
    class operator NotEqual(const A, B: TComplex): Boolean;
    class operator Negative(const A: TComplex): TComplex;
    class operator Implicit(const D: Double): TComplex;   // Double -> TComplex automatically
    function Abs: Double;
    function ToString: string;
  end;

class function TComplex.Create(ARe, AIm: Double): TComplex;
begin
  Result.Re := ARe;
  Result.Im := AIm;
end;

class operator TComplex.Add(const A, B: TComplex): TComplex;
begin
  Result.Re := A.Re + B.Re;
  Result.Im := A.Im + B.Im;
end;

class operator TComplex.Subtract(const A, B: TComplex): TComplex;
begin
  Result.Re := A.Re - B.Re;
  Result.Im := A.Im - B.Im;
end;

class operator TComplex.Multiply(const A, B: TComplex): TComplex;
begin
  Result.Re := A.Re * B.Re - A.Im * B.Im;
  Re
…

31.2 Operator names

LogicalNot / BitwiseAnd / BitwiseOr / BitwiseXor not / and / or / xor LeftShift / RightShift Inc() / Dec() TType(X) cast Initialize / Finalize custom managed record hooks Assign (Delphi) / Copy (FPC) record assignment hook

In FPC objfpc mode, operators can also be declared globally with symbols:

and inside advanced records as class operator + (const A, B: TVec2): TVec2;.

{$mode objfpc}
type
  TVec2 = record X, Y: Double; end;

operator + (const A, B: TVec2): TVec2;
begin
  Result.X := A.X + B.X;
  Result.Y := A.Y + B.Y;
end;

operator * (const A: TVec2; S: Double): TVec2;
begin
  Result.X := A.X * S;
  Result.Y := A.Y * S;
end;

31.3 Custom managed records

Records can run code on initialization, finalization, and copy — useful for RAII-style guards and smart pointers:

FPC 3.2 provides the same idea as management operators in advanced records: class operator Initialize(var aRec: TTracked);, Finalize, Copy, and AddRef.

// Delphi 10.4+
type
  TTracked = record
    Value: Integer;
    class operator Initialize(out Dest: TTracked);
    class operator Finalize(var Dest: TTracked);
  end;

class operator TTracked.Initialize(out Dest: TTracked);
begin
  Dest.Value := 0;
  Writeln('init');
end;

class operator TTracked.Finalize(var Dest: TTracked);
begin
  Writeln('finalize');
end;

31.4 Class and record helpers

A helper adds methods to an existing type without inheritance — handy for types you cannot change:

Rules and caveats:

Only one helper per type is active in a scope (the nearest one wins) — a custom helper for string or Integer hides the RTL's TStringHelper/TIntegerHelper unless it inherits from them (record helper(TStringHelper) for string). FPC trunk adds {$modeswitch multihelpers}.

Helpers can add methods, properties, and class members, but not instance fields.

FPC objfpc mode uses type helper for Integer for primitive types (with {$modeswitch typehelpers}); record helper for works in delphi mode.

Use helpers sparingly: they are a convenience, not a design tool.

program Helpers;
{$IFDEF FPC}{$mode delphi}{$modeswitch typehelpers}{$ENDIF}
{$APPTYPE CONSOLE}
uses SysUtils, Classes;

type
  TIntegerHelperEx = record helper for Integer
    function IsEven: Boolean;
    function Times(const S: string): string;
  end;

  TStringsHelper = class helper for TStrings
    function JoinWith(const Sep: string): string;
  end;

function TIntegerHelperEx.IsEven: Boolean;
begin
  Result := not Odd(Self);
end;

function TIntegerHelperEx.Times(const S: string): string;
var
  I: Integer;
begin
  Result := '';
  for I := 1 to Self do
    Result := Result + S;
end;

function TStringsHelper.JoinWith(const Sep: string): string;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to Count - 1 do
  begin
    if I > 0 then Result := Result + Sep;
    Result := Result + Strings[I];
  end;
end;

var
  N: Integer;
  SL: TStringList;
begin
  N := 4;
  Writeln(N.IsEven, ' ', N.Times('ab'));     // TRUE abababab
  SL := TStringList.Create;
  try
    SL.Add('a'); SL.Add('b'); SL.Add('c');
    Writeln(SL.JoinWith(' + '));             // a + b + c
  finally
    SL.Free;
  end;
end.