Defining the action of an operator is much like the definition of a function or procedure, only there are some restrictions on the possible definitions, as will be shown in the subsequent.
Operator overloading is, in essence, a powerful notational tool; but it is also not more than that, since the same results can be obtained with regular function calls. When using operator overloading, It is important to keep in mind that some implicit rules may produce some unexpected results. This will be indicated.
The parameter list for a comparision operator or an arithmetic operator must always contain 2 parameters. The result type of the comparision operator must be Boolean.
Operator definitions
Remark: When compiling in Delphi mode or Objfpc mode, the result identifier may be dropped. The result can then be accessed through the standard Result symbol.
If the result identifier is dropped and the compiler is not in one of these modes, a syntax error will occur.
The statement block contains the necessary statements to determine the result of the operation. It can contain arbitrary large pieces of code; it is executed whenever the operation is encountered in some expression. The result of the statement block must always be defined; error conditions are not checked by the compiler, and the code must take care of all possible cases, throwing a run-time error if some error condition is encountered.
In the following, the three types of operator definitions will be examined. As an example, throughout this chapter the following type will be used to define overloaded operators on :
type complex = record re : real; im : real; end;this type will be used in all examples.
The sources of the Run-Time Library contain a unit ucomplex, which contains a complete calculus for complex numbers, based on operator overloading.
The assignment operator defines the action of a assignent of one type of variable to another. The result type must match the type of the variable at the left of the assignment statement, the single parameter to the assignment operator must have the same type as the expression at the right of the assignment operator.
This system can be used to declare a new type, and define an assignment for that type. For instance, to be able to assign a newly defined type 'Complex'
Var C,Z : Complex; // New type complex begin Z:=C; // assignments between complex types. end;You would have to define the following assignment operator:
Operator := (C : Complex) z : complex;
To be able to assign a real type to a complex type as follows:
var R : real; C : complex; begin C:=R; end;the following assignment operator must be defined:
Operator := (r : real) z : complex;As can be seen from this statement, it defines the action of the operator := with at the right a real expression, and at the left a complex expression.
an example implementation of this could be as follows:
operator := (r : real) z : complex; begin z.re:=r; z.im:=0.0; end;As can be seen in the example, the result identifier (z in this case) is used to store the result of the assignment. When compiling in Delphi mode or objfpc mode, the use of the special identifier Result is also allowed, and can be substituted for the z, so the above would be equivalent to
operator := (r : real) z : complex; begin Result.re:=r; Result.im:=0.0; end;
The assignment operator is also used to convert types from one type to another. The compiler will consider all overloaded assignment operators till it finds one that matches the types of the left hand and right hand expressions. If no such operator is found, a 'type mismatch' error is given.
Remark: The assignment operator is not commutative; the compiler will never reverse the role of the two arguments. in other words, given the above definition of the assignment operator, the following is not possible:
var R : real; C : complex; begin R:=C; end;if the reverse assignment should be possible (this is not so for reals and complex numbers) then the assigment operator must be defined for that as well.
Remark: The assignment operator is also used in implicit type conversions. This can have unwanted effects. Consider the following definitions:
operator := (r : real) z : complex; function exp(c : complex) : complex;then the following assignment will give a type mismatch:
Var r1,r2 : real; begin r1:=exp(r2); end;because the compiler will encounter the definition of the exp function with the complex argument. It implicitly converts r2 to a complex, so it can use the above exp function. The result of this function is a complex, which cannot be assigned to r1, so the compiler will give a 'type mismatch' error. The compiler will not look further for another exp which has the correct arguments.
It is possible to avoid this particular problem by specifying
r1:=system.exp(r2);An experimental solution for this problem exists in the compiler, but is not enabled by default. Maybe someday it will be.
Arithmetic operators define the action of a binary operator. Possible operations are:
The definition of an arithmetic operator takes two parameters. The first parameter must be of the type that occurs at the left of the operator, the second parameter must be of the type that is at the right of the arithmetic operator. The result type must match the type that results after the arithmetic operation.
To compile an expression as
var R : real; C,Z : complex; begin C:=R*Z; end;one needs a definition of the multiplication operator as:
Operator * (r : real; z1 : complex) z : complex; begin z.re := z1.re * r; z.im := z1.im * r; end;As can be seen, the first operator is a real, and the second is a complex. The result type is complex.
Multiplication and addition of reals and complexes are commutative operations. The compiler, however, has no notion of this fact so even if a multiplication between a real and a complex is defined, the compiler will not use that definition when it encounters a complex and a real (in that order). It is necessary to define both operations.
So, given the above definition of the multiplication, the compiler will not accept the following statement:
var R : real; C,Z : complex; begin C:=Z*R; end;since the types of Z and R don't match the types in the operator definition.
The reason for this behaviour is that it is possible that a multiplication is not always commutative. e.g. the multiplication of a (n,m) with a (m,n) matrix will result in a (n,n) matrix, while the mutiplication of a (m,n) with a (n,m) matrix is a (m,m) matrix, which needn't be the same in all cases.
The comparision operators that can be overloaded are:
As an example, the following opetrator allows to compare two complex numbers:
operator = (z1, z2 : complex) b : boolean;the above definition allows comparisions of the following form:
Var C1,C2 : Complex; begin If C1=C2 then Writeln('C1 and C2 are equal'); end;
The comparision operator definition needs 2 parameters, with the types that the operator is meant to compare. Here also, the compiler doesn't apply commutativity; if the two types are different, then it necessary to define 2 comparision operators.
In the case of complex numbers, it is, for instance necessary to define 2 comparsions: one with the complex type first, and one with the real type first.
Given the definitions
operator = (z1 : complex;r : real) b : boolean; operator = (r : real; z1 : complex) b : boolean;the following two comparisions are possible:
Var R,S : Real; C : Complex; begin If (C=R) or (S=C) then Writeln ('Ok'); end;Note that the order of the real and complex type in the two comparisions is reversed.