Type Conversions and Object Oriented Fortran

The addition of full object oriented programming in Fortran 2003 gives us the ability to create generic containers for objects that extend a base class. Since we are storing a pointer to the base class, we will have to convert that pointer to the dynamic type later when we want to use it. This post describes how I do that in Fortran.

To be specific, here’s how I might set up a simple finite element mesh structure

      TYPE FEMBase
      END TYPE FEMBase

      TYPE, EXTENDS(FEMBase) :: FEMNode
         REAL :: x(2)

      TYPE, EXTENDS(FEMBase) :: FEMEdge
CLASS(FEMNode), POINTER :: startNode, endNode

      TYPE EdgePointer
END TYPE EdgePointer

      TYPE, EXTENDS(FEMBase) :: FEMElement
TYPE(EdgePointer) :: edges(4)

The base class will store things common to all extensions, in this case just the id of the object.  The structure here defines quadrilateral elements in terms its four edges, which are defined in terms of their two end nodes.

Next, I need a mechanism to store instances of these objects. I’d ususally store things in some collection class, say a linked list. To make it the list generic, I'd store a pointer to the base class. (Note that we could store an unlimited polymorphic object using CLASS(*), but the base object class can be implemented to do something useful like reference counting.) I then would save all the nodes, edges and elements in their own collection:

TYPE(Collection) :: nodeCollection, edgeCollection, elementCollection

And I would add each mesh quantity to its appropriate collection by way of its FEMBase object pointer. For instance, I might create and save a node in its collection by

     CLASS(FEMNode), POINTER :: node
CALL node % init(0.1,0.2,0.3)
     ptr => node
CALL nodeCollection % add(ptr)

Here I've used type bound procedures like I described in my previous post. I can do the same thing with edges and elements.

At some point, I'll want to retrieve one of the stored objects, which will return a pointer to the base class, e.g.

     CLASS(FEMNode), POINTER :: node
ptr => nodeCollection % objectWithID(42)

When I retrieve an object from a generic collection, I must convert its type to the proper subclass in order to access its unique properties and procedures. (In other languages this would be known as casting the object.) Fortran provides a rather unique mechanism to do this  by way of the SELECT TYPE construct, which looks like SELECT CASE. One of the interesting features of examples that I have found on the select type construct is that they seem to imply that we have to know all of the subclasses in order to use it. An example can be found here. Another feature is that it is quite verbose.

In the example above, though, I know that ptr is a pointer to a node, so I just want a short and simple way to convert this to a pointer to a node.

My inspiration came from the fact that Fortran has always had type conversions, and that they are performed as function calls. For instance, i = int(x) converts a real, x, to an integer, i. So in my approach, I implement object type conversions by function calls. 

As part of my class definitions, I include a conversion function. To convert from a FEMBase pointer to a FEMNode pointer, for instance, I write the function that implements the SELECT TYPE block for the desired subclass

         FUNCTION nodeFromBase(baseObj) RESULT(rObj)
            IMPLICIT NONE
            CLASS(FEMBase), POINTER  :: baseObj
            rObj =>
SELECT TYPE (e => baseObj)
TYPE is (FEMNode)
                  rObj => e

This function takes a pointer to the base class and returns a pointer to the subclass. (If it turns out that a mistake has been made and the pointer is not of the correct type, then it returns an unassociated pointer.) I write similar functions for the FEMEdge and FEMElement derived types.

With the type conversion function, it is now quick and easy to do the conversion

     CLASS(FEMNode), POINTER :: node
ptr  => nodeCollection % objectWithID(42)
     node => nodeFromBase(ptr)
IF(.NOT.ASSOCIATED(node)) CALL throwError

For safety, I can include a check on whether or not the node is associated and set an error condition, for example, if there is no node 42.

© Nocturnal Aviation Software 2011-2017. Mac and Mac OS are trademarks of Apple Inc., registered in the U.S. and other countries.  All other trademarks are the property of their respective owners. Privacy Policy.