C/C++ and Fortran

C/C++ and Fortran

Connect, learn, share, and engage with IBM Power.

 View Only

Object-Oriented Fortran: Does Fortran support operator overloading?

By Archive User posted Tue February 21, 2012 11:18 AM

  

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.
 
0 comments
2 views

Permalink