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
INTEGER :: id
END TYPE FEMBaseTYPE, EXTENDS(FEMBase) :: FEMNode
REAL :: x(2)
END TYPE FEMNodeTYPE, EXTENDS(FEMBase) :: FEMEdge
CLASS(FEMNode), POINTER :: startNode, endNode
END TYPE FEMEdgeTYPE EdgePointer
CLASS(FEMNode), POINTER :: edge
END TYPE EdgePointerTYPE, EXTENDS(FEMBase) :: FEMElement
TYPE(EdgePointer) :: edges(4)
END TYPE FEMElement
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
CLASS(FEMBase), POINTER :: ptr
ALLOCATE(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
CLASS(FEMBase), POINTER :: ptr
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
CLASS(FEMNode), POINTER :: rObj
rObj => NULL()
SELECT TYPE (e => baseObj)
TYPE is (FEMNode)
rObj => e
CLASS DEFAULT
END SELECT
END FUNCTION nodeFromBase
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
CLASS(FEMBase), POINTER :: ptr
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.