pointersfortranderived-typesfortran2008

Pointers to arrays as member variables in Fortran derived type


In Fortran, it is not possible to make a member variable of a derived type a target. (I guess this has to do with the standard not specifying how a derived type is stored in memory?) However, I can have a pointer as a member variable and associate pointers with pointers. Like I do in the example below.

module DataMod

   type DataType

      real(8), private, dimension(:,:), pointer, contiguous :: A
      real(8), private, dimension(:,:), pointer, contiguous :: B

      integer :: n

   contains 

      procedure :: alloc
      procedure :: set
      procedure :: print_
      final :: dealloc

   end type DataType

   interface DataType
      procedure :: NewDataType
   end interface DataType

   contains

   function NewDataType(dimension_) result(new)

      integer, intent(in) :: dimension_

      type(DataType) :: new

      new%n = dimension_

   end function NewDataType

   subroutine alloc(dataObject)

      class(DataType) :: dataObject

      allocate(dataObject%A(dataObject%n,dataObject%n))
      allocate(dataObject%B(dataObject%n,dataObject%n))

   end subroutine alloc

   subroutine set(dataObject, datas, choice)

      class(DataType) :: dataObject

      real(8), dimension(dataObject%n,dataObject%n), intent(in) :: datas

      character(len=1), intent(in) :: choice

      real(8), dimension(:,:), pointer :: dataPointer
      integer :: i,j

      if(choice .eq. 'A') then
         datapointer => dataObject%A
      elseif(choice .eq. 'B') then
         datapointer => dataObject%B
      else
         stop
      endif

      do j = 1,dataObject%n
         do i = 1,dataObject%n
            datapointer(i,j) = datas(i,j)
         enddo
      enddo

   end subroutine set

   subroutine print_(dataObject)

      class(DataType), intent(in) :: dataObject

      print *, 'A'
      print *, dataObject%A(1:dataObject%n,1:dataObject%n)
      print * 
      print *, 'B'
      print *, dataObject%B(1:dataObject%n,1:dataObject%n)

   end subroutine print_

   subroutine dealloc(dataObject)

      type(DataType) :: dataObject

      deallocate(dataObject%A)
      deallocate(dataObject%B)

   end subroutine dealloc

end module DataMod


program DataTest

   use DataMod, only: DataType

   implicit none

   real(8), dimension(2,2) :: testArray
   type(DataType) :: testType

   testType = DataType(2)
   call testType%alloc()

   testArray(1,1) = 1
   testArray(2,1) = 2
   testArray(1,2) = 3
   testArray(2,2) = 4

   call testType%set(testArray, 'A')

   testArray(1,1) = 5
   testArray(2,1) = 6
   testArray(1,2) = 7
   testArray(2,2) = 8

   call testType%set(testArray, 'B')

   call testType%print_()

end program DataTest

In the set routine, I use an if statement to set a pointer to decide if it should dump the incoming matrix in A or B. In the program I'm currently working on, I must decide which combination of four different matrices to multiply together and setting a pair of pointers is much nicer than writing 16 almost identical calls to dgemm.

My question is if there are any problems with this approach besides the normal dangers of dangling pointers etc. or a way to do this without pointers? The arrays should not be accessed from outside the object. Are there any performance issues?


Solution

  • Components in a type definition may not be declared with the TARGET attribute (beyond the missing syntax, that would be inconsistent with other concepts and rules in the current language), but if a variable of derived type has the TARGET attribute, then all of its subobjects have the TARGET attribute too. For a type definition:

    type DataType
      real(8), private, dimension(:,:), allocatable :: A
      real(8), private, dimension(:,:), allocatable :: B
      ...
    

    The procedure set could be written...

     subroutine set(dataObject, datas, choice)
       class(DataType), TARGET :: dataObject
       real(8), dimension(dataObject%n,dataObject%n), intent(in) :: datas
       character(len=1), intent(in) :: choice
       real(8), dimension(:,:), pointer :: dataPointer
    
       ! require dataobject%A and ..%B to already be allocated.
       if(choice .eq. 'A') then
         datapointer => dataObject%A
       elseif(choice .eq. 'B') then
         datapointer => dataObject%B
       else
         stop
       endif
    
       datapointer = datas    ! or some other operation.
       ...
    

    (dataPointer could be declared contiguous, the allocatable arrays that it gets pointed at are always contiguous.)

    An actual argument without the TARGET attribute may be associated with a dummy argument with the TARGET attribute. If this is the case, then pointers associated with the dummy argument become undefined when execution of the procedure completes. (Such pointers may also become undefined in some cases, even when the actual argument has the TARGET attribute - see F2018 15.5.2.4p8 on for details - but these cases don't apply to scalars.)

    Consequently, in the general case, if a pointer to one of the components of an object of derived type needs to outlive a procedure like set above (e.g. if dataPointer was not local to set) and you can't ensure that the actual argument will always have the TARGET attribute, then the original method using pointer components may be more appropriate. The implementation in the question appears to be ok - though I would suggest making the finalizer IMPURE ELEMENTAL to make things more robust to future changes.