Saturday, March 31, 2007

DN4DP#5: Redefining the operators

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book. Last time we talked about the new record syntax. This time we will look at the exciting new operator overloading capabilities.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Redefining the operators

Long-time Delphi programmers have looked at the operator overloading features of other languages (such as C++) with both fear and envy. The ability for user code to redefine the meaning of operators applied to an instance of a specific class is powerful and elegant if used properly, but it can be highly confusing and erroneous if used improperly.

Delphi now supports operator overloading. This is an advanced concept where a class or (more commonly, and the only kind supported in Win32) a record can have a special method called when a specific operator (such as +, -, /, *, div, mod, etc) is applied to an instance of the class or record. To define operator overloads you must define class operator functions with specific names for each operator. See the Delphi language documentation for the full list of class operator names.

In addition to normal operators, implicit and explicit casts (or conversions) can be implemented. The OperatorOverloading project demonstrates the potentially confusing aspects of operator overloading. It defines a TStrangeInt record where the operator semantics have been reversed:

type
TStrangeInt = record
public
Value: Integer;
class operator Add(const Left, Right: TStrangeInt):
TStrangeInt; inline;
class operator Subtract(const Left, Right: TStrangeInt):
TStrangeInt; inline;
class operator Multiply(const Left, Right: TStrangeInt):
TStrangeInt; inline;
class operator Divide(const Left, Right: TStrangeInt):
TStrangeInt; inline;
class operator Implicit(const AValue: Integer):
TStrangeInt; inline;
class operator Implicit(const AValue: TStrangeInt):
Integer; inline;
class operator Implicit(const AValue: TStrangeInt):
string; inline;
class operator Explicit(const AValue: Double):
TStrangeInt; inline;
end;

The implementation of a class operator method should create and return a new record or class instance and not modify any of the parameters. Often operator methods are very short and simple and thus perfect inline candidates[1] (inline routines are covered later in this Chapter).

class operator TStrangeInt.Add(const Left, Right: 
TStrangeInt): TStrangeInt;
begin
Result.Value := Left.Value - Right.Value;
end;

Invoking the overloaded operators is just a matter of declaring an instance of the record type and using the standard Delphi operators on it.

var
Strange: TStrangeInt;
StrangeResult: TStrangeInt;
begin
Strange := 42;
StrangeResult := Strange + Strange * 3;
end;

Because of the reversed TStrangeInt implementation, StrangeResult will be 28 instead of the expected 168. See Chapter 7 for more details on operator overloading.

Tip For a more complete example of operator overloading, see the Borland.Vcl.Complex .NET unit (or the Vassbotn.Vcl.Complex unit in the Demos\DelphiWin32\VCLWin32\ComplexNumbers\ folder for the corresponding Win32 unit).




[1] Marking overloaded operators with inline is purely optional, of course. Currently (Delphi 2006) it seems like the compiler manages to inline all simple operators except the Implicit and Explicit conversion operators."

1 comment:

Anonymous said...

Hi,

Just realised that the Delphi compiler is inconsistent with the use of the "overload" directive.
For example one would think that "overload" should be required here but it isn't, but if these were "class function... " then "overload" would be required.
---
class operator Implicit(const AValue: Integer):
TStrangeInt; inline;
class operator Implicit(const AValue: TStrangeInt):
Integer; inline;
class operator Implicit(const AValue: TStrangeInt):
string; inline;
---

I think the same can be said with overloading the default array parameter of a class where "overload" is not allowed on the same signature of the default parameter even though strictly one would think it should since one is overloading.

I think this is an inconsistency in the Delphi compiler that should be "fixed" in subsequent versions. The "overload" directive makes it more clearer (and safer) that one is overloading the same function, etc. to avoid subtle bugs, especially when the same function (but different parameters) is spread out in the code, so one viewing the code might not even be aware there's an overloaded version of that function/operator, etc.

Steve



Copyright © 2004-2007 by Hallvard Vassbotn