Search code examples
fortrangfortranintel-fortran

Pointers in pure functions


In order to traverse a linked list in Fortran, I use a pointer to the current element that is moved to the next one inside a loop. Trying to apply this inside a pure function that operates on said linked list results in an error.

Example:

module list
  implicit none

  ! Node
  type n_list
    integer               :: val
    type(n_list),pointer  :: next => NULL()
  end type

  ! Linked list
  type t_list
    type(n_list),pointer  :: head
  end type

contains

  pure function in_list( list, val ) result(res)
    implicit none
    class(t_list),intent(in)  :: list
    integer,intent(in)        :: val
    logical                   :: res
    type(n_list),pointer      :: cur

    res = .true.
    ! Traverse the list
    cur => list%head
    do while ( associated(cur) )
      if ( cur%val == val ) return 
      cur => cur%next
    enddo

    ! Not found
    res = .false.
  end function
end module

Results in

    cur => list%head
         1
Error: Bad target in pointer assignment in PURE procedure at (1)

I am aware of the rationale behind the error/warning, and that it is difficult to ensure that the arguments of the function are not changed when using pointers (Fortran 2008, ch. 12.7 "Pure procedures", esp. C1283). In this case, though, list is never changed.

Is it possible to tell the compiler (ifort and gfortran) that intent(in) is not violated?


Solution

  • OK, I found a solution using the transfer intrinsic. The main idea is to clone the list struct (without the data, I checked), and use the pointer to the first node (unchanged) as a start value. Yeah, it is a loop-hole, but both ifort and gfortran accept this without warnings.

    module list_mod
      implicit none
    
      ! Node
      type n_list
        integer               :: val
        type(n_list),pointer  :: next => NULL()
      end type
    
      ! Linked list
      type t_list
        type(n_list),pointer  :: head
      end type
    
    contains
    
      pure function getHead(list) result(res)
        implicit none
        class(t_list),intent(in)  :: list
        type(n_list),pointer      :: res
        type(t_list),pointer      :: listPtr
    
        ! Create a copy of pointer to the list struct
        allocate( listPtr )
        listPtr = transfer( list, listPtr )
    
        ! Set the pointer
        res => listPtr%head
    
        ! Free memory
        deallocate( listPtr )
      end function
    
      pure function in_list( list, val ) result(res)
        implicit none
        class(t_list),intent(in)  :: list
        integer,intent(in)        :: val
        logical                   :: res
        type(n_list),pointer      :: cur
    
        res = .true.
    
        ! Traverse the list
        cur => getHead(list)
        do while ( associated(cur) )
          if ( cur%val == val ) return
          cur => cur%next
        enddo
    
        ! Not found
        res = .false.
      end function
    
    end module
    
    program test
      use list_mod
      implicit none
      integer,parameter     :: MAXELEM = 10000000
      integer               :: i
      type(t_list)          :: list
      type(n_list),pointer  :: cur
    
      ! Fill list
      list%head => NULL()
      allocate( list%head )
      list%head%val = 1
    
      cur => list%head
      do i=2,MAXELEM
        allocate( cur%next )
        cur%next%val = i
        cur => cur%next
      enddo !i
    
      print *,'is MAXELEM/2 in list? ', in_list( list, MAXELEM/2 )
      print *,'is MAXELEM+1 in list? ', in_list( list, MAXELEM+1 )
    end program