Operator overloading is a key ingredient of object-oriented programming. Fortran (starting from Fortran 90) supports operator overloading through defined operator which allows you to extend an intrinsic operator or define a new operator. For example, you can extend "+" to perform "var1 = var2 + var3" where var1, var2, and var3 are derived-type objects. Likewise, you can define a new operator, such as ".UNION.", to compute the union of two sets " set3 = set1 .UNION. set2", where set1, set2, and set3 could be arrays or objects of a user-defined type representing sets. In this article, I am going to illustrate how to define an operator to perform operations that the language does not support intrinsically.

I will start with a simple example to describe the mechanism and will mention the related language rules at the end. Consider an application that processes points in three dimensions where a point is represented by the following derived type.

type point_t

real :: x

real :: y

real :: z

end type point_t

Consider that the application requires adding an offset of type real to each dimension of a point. Let us extend the '+' operator to perform the intended operation.

Step 1: Define the meaning of applying '+' on objects of type point_t and real:

- This translates to defining a function, say add_

offset_to_point, that will perform the intended operation.

function add_offset_to_point(op1, op2)

type(point_t), INTENT(IN) :: op1

real, INTENT(IN) :: op2

type(point_t) :: add_offset_to_point

add_offset_to_point%x = op1%x + op2

add_offset_to_point%y = op1%y + op2

add_offset_to_point%z = op1%z + op2

end function add_offset_to_point

Step 2: Indicate that the function add_offset_to_point will be used as '+' operation:

- This translates to providing a generic interface for the function add_offset_to_point and specifying OPERATOR in the generic specification.

INTERFACE OPERATOR (+)

procedure add_offset_to_point

END INTERFACE OPERATOR (+)

Step 3: Using the newly defined operator the same way as an intrinsic '+' operator is used.

type(point_t) :: pt1, pt2, pt3

real :: off

...

pt1 = pt2 + off

The compiler interprets "pt3 = pt1 + off" as "pt3 = add_offset_to_point(pt1, off)".

The following example consolidates the pieces from above into a complete program.

MODULE POINT

TYPE point_t

REAL :: x

REAL :: y

REAL :: z

END TYPE point_t

! Step 2: Extends binary "+"

INTERFACE OPERATOR (+)

PROCEDURE add_offset_to_point

END INTERFACE OPERATOR (+)

CONTAINS

! Step 1: Provides interpretation of defined operator

FUNCTION add_offset_to_point(op1, op2)

TYPE(point_t), INTENT(IN) :: op1

REAL, INTENT(IN) :: op2

TYPE(point_t) :: add_offset_to_point

add_offset_to_point%x = op1%x + op2

add_offset_to_point%y = op1%y + op2

add_offset_to_point%z = op1%z + op2

print *, "Adding real to point"

END FUNCTION add_offset_to_point

END MODULE POINT

PROGRAM SIMULATOR

USE POINT

TYPE(point_t) :: pt1, pt2

REAL :: offset

pt2%x = 10

pt2%y = 20

pt2%z = 30

offset = 100.0

pt1 = pt2 + offset ! Step 3: Using defined operator

print *, pt1%x, pt1%y, pt1%z

END PROGRAM SIMULATOR

The program produces the following output.

> xlf2003 defined-op.f

** point === End of Compilation 1 ===

** simulator === End of Compilation 2 ===

1501-510 Compilation successful for file defined-op.f.

> ./a.out

Adding real to point

110.0000000 120.0000000 130.0000000

Now, consider that the application needs to add (dimension wise) coordinates of two point_t type objects as shown below.

pt3 = pt1 + pt2

In order to support the new operation, we just need to define another function, say add_point_to_point, to perform the operation, and add the interface of add_point_to_point to the generic interface ('+') we created in Step 2.

INTERFACE OPERATOR (+)

PROCEDURE add_offset_to_point

PROCEDURE add_point_to_point ! newly added

END INTERFACE OPERATOR (+)

FUNCTION add_point_to_point(op1, op2)

TYPE(point_t), INTENT(IN) :: op1, op2

TYPE(point_t) :: add_point_to_point

! Perform the addition

END FUNCTION add_point_to_point

The last step demonstrates how an operator can be extended to operate on multiple combinations of operand types. The combinations of operand types must be such that op1 + op2 resolves to a unique specific binding of ('+') in accordance with the language rules for generic interface.

For completeness, let us define a unary operator ".XCOORD." to retrieve the value of the x coordinate of a point. The interface and the new function are as follows.

INTERFACE OPERATOR (.XCOORD.)

PROCEDURE get_x_coord

END INTERFACE OPERATOR (.XCOORD.)

FUNCTION get_x_coord(op1)

TYPE(point_t), INTENT(IN) :: op1

REAL :: get_x_coord

get_x_coord = op1%x

END FUNCTION get_x_coord

Now that we have seen all the required steps, let us consider an alternative to Step 2. The generic interface of a defined-operator can be bound to a derived-type. For example, we could represent the points by the following type.

TYPE point_t

REAL :: x

REAL :: y

REAL :: z

contains

procedure :: add_offset_to_point, add_point_to_point

generic :: operator(+) => add_offset_to_point, add_point_to_point

procedure :: get_x_coord

generic :: operator(.XCOORD.) => get_x_coord

END TYPE point_t

The difference from the previous method is that the compiler interprets "pt1 + pt2" as "pt1%add_point_to_point(pt2)". When an operator is defined using generic binding, the dummy arguments of the function that implements the operator must satisfy the constraints of a type-bound procedure, such as the constraints on the passed-object dummy arguments. However, no syntactic change is needed in the usage of the defined operator.

I am going to conclude this article by mentioning the language rules that govern defined-operators.

R1) A defined-operator shall be unary or binary. All procedures specified in the generic interface corresponding to a unary (binary, respectively) intrinsic operator must be functions with one (two, respectively) arguments. In our example, both "add_offset_to_point" and "add_point_to_point" extend binary intrinsic "+" operation and have two arguments each.

R2) The dummy arguments of these function must be non optional, and have INTENT(IN) attribute.

R3) An extended intrinsic operator must operate on the same number of operands as the original intrinsic operator. Therefore, an extended '*' operator must be binary whereas an extended ".NOT." must be unary.

R4) When a function extends an intrinsic operation, at least one dummy argument must have a type, kind parameter, or rank that distinguish the extended operation from the intrinsic operation. In our example, the type of the dummy argument "op1" of function "add_offset_to_point" distinguishes extended addition "pt2 + off" from an intrinsic addition like " 2.0 + off".

R5) Relational operators have two forms. For example "<" has the same interpretation as ".LE.". Extending one form automatically extends both forms.