Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

polymorphic type question

martymike
Novice
205 Views


Consider the following, greatly simplified, sections of code

type :: obj_rcd
   type(net_rcd),dimension(:),allocatable :: rcd
   integer :: cntr=0
   type(rcd_desc),dimension(:), pointer :: def
end type

type,extends(obj_rcd) :: area_rcd
contains
   procedure increment_counter
end type

subroutine increment_counter(this)
call increment_counter_object(this)
end subroutine increment_counter

subroutine increment_counter_object(obr)
type(obj_rcd) :: obr
if (obr%cntr >= size(obr%rcd)) call extend_obj(obr%rcd,obr%def)
obr%cntr = obr%cntr+1
end subroutine increment_counter_object

This fails to compile due to type mismatch calling increment_counter_object, and this makes perfect sense. I found that these two modifications both build and both seem to work properly:

[1]

subroutine increment_counter(this)
class(area_rcd),target :: this
class(obj_rcd),pointer :: that
that=>this
call increment_counter_object(that)
end subroutine increment_counter


subroutine increment_counter_object(obr)
type(obj_rcd) :: obr
if (obr%cntr >= size(obr%rcd)) call extend_obj(obr%rcd,obr%def)
obr%cntr = obr%cntr+1
end subroutine increment_counter_object


[2]

subroutine increment_counter(this)
call increment_counter_object(this)
end subroutine increment_counter


subroutine increment_counter_object(obr)
class(obj_rcd) :: obr
if (obr%cntr >= size(obr%rcd)) call extend_obj(obr%rcd,obr%def)
obr%cntr = obr%cntr+1
end subroutine increment_counter_object


I suspect that there is a difference between these two. I can make a reasoned guess as to what it might be, but I'm looking for a more definitive explanation. Anyone have one?

 

0 Kudos
1 Reply
IanH
Honored Contributor II
205 Views

You very likely want your option two, but there's also an option three.

subroutine increment_counter(this)
  class(area_rcd) :: this
  call increment_counter_object(this%obj_rcd)  ! reference parent component.
end subroutine increment_counter

subroutine increment_counter_object(obr)
  type(obj_rcd) :: obr
  if (obr%cntr >= size(obr%rcd)) call extend_obj(obr%rcd,obr%def)
  obr%cntr = obr%cntr+1
end subroutine increment_counter_object

There is a general requirement that a dummy argument must be type compatible with the corresponding actual argument.  For non-polymorphic dummy arguments - TYPE(xxx) - this means that the declared type of the dummy and actual arguments must be the same.  For polymorphic dummy arguments the declared type of the actual argument must be an extension of the declared type of the dummy argument (where the definition of "extension" is such that a type is considered an extension of itself).

(There are other restrictions for allocatable and pointer dummy arguments, and if coarray like stuff is in use, but they are not pertinent here.)

Your original example, with the non-polymorphic dummy in increment_counter_object, does not satisfy type compatibility - the declared type of the actual argument is `area_rcd`, while the dummy argument is `obj_rcd`, and they are not the same.  The three options do satisfy that restriction through various means.

The issue with your option one and option three above is that because the dummy argument is non-polymorphic, inside the increment_counter_object procedure you completely disregard the original dynamic type of the actual argument - that procedure only deals with the parent bit of the actual argument.  The syntax of option three makes that explicit - with the reference to the parent component.  Option one does that implicitly - because the rules of the language permit a polymorphic actual argument to be associated with a non-polymorphic dummy, so long as the declared types match (the use of pointers is incidental to this - you could also use an intermediate procedure).

The ability to completely ignore the non-parent bits of an object is known in some contexts as "slicing" (not the same thing as Fortran array slicing).  It can be problematic if the object as a whole is not robust to being sliced.  The ability to implicitly slice an object in this manner in modern Fortran is regarded by some as a defect in the language, but as usual with these sorts of things, there's a trade-off - sometimes the ability to do this sort of stuff might have its place.  Permit me to sit on the fence.

When you declare the dummy argument to be polymorphic, the code inside the procedure is still aware of the dynamic type of the associated object.  Operations on the dummy object are limited to those of its declared type, but things like binding dispatch still depend on the dynamic type.  In situations more complicated than your example code, this may be required to keep data members in the extended type consistent with data members in the object corresponding to the parent type.

An attempt at an example (which is silly for various reasons, but it is just for the sake of example).  If an object of the extension type is sliced and then operated on, it can become internally inconsistent.

MODULE m
  IMPLICIT NONE
  
  PRIVATE
  
  TYPE, PUBLIC :: Parent
    INTEGER :: i
  CONTAINS
    PROCEDURE :: Scale => parent_Scale
  END TYPE Parent
  
  TYPE, PUBLIC, EXTENDS(Parent) :: Extension
    ! Lets assume that we want to maintain cosistency between r and i.
    REAL :: r
  CONTAINS
    PROCEDURE :: Scale => extension_Scale
  END TYPE Extension
  
  PUBLIC :: ProbablyNotGood
  PUBLIC :: ProbablyOk
CONTAINS
  SUBROUTINE parent_Scale(object, factor)
    CLASS(Parent), INTENT(INOUT) :: object
    REAL, INTENT(IN) :: factor
    object%i = object%i * factor
  END SUBROUTINE parent_Scale
  
  SUBROUTINE extension_Scale(object, factor)
    CLASS(Extension), INTENT(INOUT) :: object
    REAL, INTENT(IN) :: factor
    
    CALL parent_Scale(object, factor)
    object%r = object%r * factor
  END SUBROUTINE extension_Scale
  
  SUBROUTINE ProbablyNotGood(object)
    TYPE(Parent), INTENT(INOUT) :: object
    
    CALL object%Scale(2.0)
  END SUBROUTINE ProbablyNotGood
  
  SUBROUTINE ProbablyOk(object)
    CLASS(Parent), INTENT(INOUT) :: object
    
    CALL object%Scale(2.0)
  END SUBROUTINE ProbablyOk
END MODULE m

PROGRAM p
  USE m
  TYPE(Extension) :: a, b, c, d
  a = Extension(1, 1.0)
  b = a
  
  ! Here at least we are explicitly slicing, so there is some hint 
  ! to the reader that only the Parent bit of the object is being 
  ! operated on.
  CALL ProbablyNotGood(a%Parent)
  PRINT "(A,': ',*(G0,:,','))", 'a', a
  
  CALL ProbablyOk(b)
  PRINT "(A,': ',*(G0,:,','))", 'b', b
END PROGRAM p

 

0 Kudos
Reply