Subsections


9 Operator overloading

1 Introduction

Free Pascal supports operator overloading. This means that it is possible to define the action of some operators on self-defined types, and thus allow the use of these types in mathematical expressions.

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.

2 Operator declarations

To define the action of an operator is much like defining a function:

Operator definitions

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{operator\ def...
...
\)\lit*{:}
\synt{result\ type}
\lit*{;}
\synt{subroutine\ block}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{assignment\ o...
...r\ definition}
\lit*{:=}
\lit*{(}
\synt{value\ parameter}
\lit*{)}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{arithmetic\ o...
...
\lit*{/}\\
\lit*{**}
\)\lit*{(}
\synt{parameter\ list}
\lit*{)}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{comparision\ ...
...rb+ > + \\
\verb+ >= +
\)\lit*{(}
\synt{parameter\ list}
\lit*{)}\end{syntdiag}
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.

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.

3 Assignment operators

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;
The following assignment operator would have to be defined:
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.

4 Arithmetic operators

Arithmetic operators define the action of a binary operator. Possible operations are:

multiplication
to multiply two types, the * multiplication operator must be overloaded.
division
to divide two types, the / division operator must be overloaded.
addition
to add two types, the + addition operator must be overloaded.
substraction
to substract two types, the - substraction operator must be overloaded.
exponentiation
to exponentiate two types, the ** exponentiation operator must be overloaded.

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.

5 Comparision operator

The comparision operator can be overloaded to compare two different types or to compare two equal types that are not basic types. The result type of a comparision operator is always a boolean.

The comparision operators that can be overloaded are:

equal to
(=) to determine if two variables are equal.
less than
($<$) to determine if one variable is less than another.
greater than
($>$) to determine if one variable is greater than another.
greater than or equal to
($>=$) to determine if one variable is greater than or equal to another.
less than or equal to
($<=$) to determine if one variable is greater than or equal to another.
There is no separate operator for unequal to ($<>$). To evaluate a statement that contans the unequal to operator, the compiler uses the equal to operator (=), and negates the result.

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.



Free Pascal Compiler
2001-09-22