Originally posted by: TariqueIslam
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.