Search code examples
oopfortranencapsulationgfortran

Does fortran permit inline operations on the return value of a function?


I am trying to design a data structure composed of objects which contain, as instance variables, objects of another type.

I'd like to be able to do something like this:

CALL type1_object%get_nested_type2_object()%some_type2_method()

Notice I am trying to immediately use the getter, get_nested_type2_object() and then act on its return value to call a method in the returned type2 object.

As it stands, gfortran v4.8.2 does not accept this syntax and thinks get_nested_type2_object() is an array reference, not a function call. Is there any syntax that I can use to clarify this or does the standard not allow this?

To give a more concrete example, here is some code illustrating this:

furniture_class.F95:

MODULE furniture_class
   IMPLICIT NONE
   TYPE furniture_object
      INTEGER :: length
      INTEGER :: width
      INTEGER :: height
   CONTAINS
      PROCEDURE :: get_length
   END TYPE furniture_object

   CONTAINS

   FUNCTION get_length(self)
      IMPLICIT NONE
      CLASS(furniture_object) :: self
      INTEGER                 :: get_length

      get_length = self%length
   END FUNCTION
END MODULE furniture_class

Now a room object may contain one or more furniture objects.

room_class.F95:

MODULE room_class
   USE furniture_class
   IMPLICIT NONE

   TYPE :: room_object
      CLASS(furniture_object), POINTER :: furniture
   CONTAINS
      PROCEDURE :: get_furniture
   END TYPE room_object

   CONTAINS

   FUNCTION get_furniture(self)
      USE furniture_class
      IMPLICIT NONE
      CLASS(room_object) :: self
      CLASS(furniture_object), POINTER :: get_furniture

      get_furniture => self%furniture
   END FUNCTION get_furniture
END MODULE room_class

Finally, here is a program where I attempt to access the furniture object inside the room (but the compiler won't let me):

room_test.F95

PROGRAM room_test
   USE room_class
   USE furniture_class
   IMPLICIT NONE

   CLASS(room_object), POINTER :: room_pointer
   CLASS(furniture_object), POINTER :: furniture_pointer

   ALLOCATE(room_pointer)
   ALLOCATE(furniture_pointer)

   room_pointer%furniture => furniture_pointer

   furniture_pointer%length = 10

   ! WRITE(*,*) 'The length of furniture in the room is', room_pointer%furniture%get_length() - This works.
   WRITE(*,*) 'The length of furniture in the room is', room_pointer%get_furniture()%get_length() ! This line fails to compile

   END PROGRAM room_test

I can of course directly access the furniture object if I don't use a getter to return the nested object, but this ruins the encapsulation and can become problematic in production code that is much more complex than what I show here.

Is what I am trying to do not supported by the Fortran standard or do I just need a more compliant compiler?


Solution

  • What you want to do is not supported by the syntax of the standard language.

    (Variations on the general syntax (not necessarily this specific case) that might apply for "dereferencing" a function result could be ambiguous - consider things like substrings, whole array references, array sections, etc.)

    Typically you [pointer] assign the result of the first function call to a [pointer] variable of the appropriate type, and then apply the binding for the second function to that variable.

    Alternatively, if you want to apply an operation to a primary in an expression (such as a function reference) to give another value, then you could use an operator.

    Some, perhaps rather subjective, comments:

    • Your room object doesn't really contain a furniture object - it holds a reference to a furniture object. Perhaps you use that reference in a manner that implies the parent object "containing" it, but that's not what the component definition naturally suggests.

      (Use of a pointer component suggests that you want the room to point at (i.e. reference) some furniture. In terms of the language, the object referenced by a pointer component is not usually considered part of the value of the parent object of the component - consider how intrinsic assignment works, restrictions around modifying INTENT(IN) arguments, etc.

      A non-pointer component suggests to me that the furniture is part of the room. In a Fortran language sense an object that is a non-pointer component it is always part of the value of the parent object of the component.

      To highlight - pointer components in different rooms could potentially point at the same piece of furniture; a non-pointer furniture object is only ever directly part of one room.)

    • You need to be very careful using functions with pointer results. In the general case, is it:

      p = some_ptr_function(args)
      

      (and perhaps I accidentally leak memory) or

      p => some_ptr_function(args)
      

      Only one little character difference, both valid syntax, quite different semantics. If the second case is what is intended, then why not just pass the pointer back via a subroutine argument? An inconsequential difference in typing and it is much safer.

    A general reminder applicable to some of the above - in the context of an expression, evaluation of a function reference yields a value. Values are not variables and hence you are not permitted to vary [modify] them.