Search code examples
pointersinterfacefortrangeneric-programming

Procedure pointer to interfaced/overloaded procedure


I am using procedure overloading and interfaces in order to achieve some sort of genericity in a Fortran program.
For this, I have a module which contains a number of procedures, all duplicated in order to be able to change the variable types. I also have at the beginning of the module, a series of interfaces of the type:

    interface norm
      module procedure &
          norm_r8, &
          norm_c
    end interface

Now my problem is that I am trying to reference norm using a procedure pointer, as such (in a different module):

procedure(), POINTER :: pNorm => NULL()
pNorm => norm

However, in this situation, gfortran gives me an error saying that I have an undefined reference to norm. If I point to norm_r8 or norm_c, no problem. But since the part of the code that assigns the pointer is not aware of the type of the variables that will be used when norm is called, I need to point to the generic name! Is there a way to point towards an overloaded procedure?


Solution

  • As far as I can tell, a procedure pointer is not allowed to point at a generic interface. The standard only mentions procedures with the EXTERNAL attribute, a module procedures, or certain intrinsic procedures may be associated with a procedure pointer (C1220, ISO/IEC 1539-1:2010). Gfortran also issues a helpful error message for your case:

    Error: Procedure pointer target 'norm' at (1) must be either an intrinsic, 
           host or use associated, referenced or have the EXTERNAL attribute
    

    It also makes sense that you cannot associate to an interface, but only a procedure. An interface is only used in the procedure(INTERFACE) statement to give an explicit interface to the procedures it can point at.

    This shouldn't be a showstopper for you, as the purpose of a generic interface can negate your need for a pointer. As long as all potential calls the pointer would be used for are unique in type, kind, rank and number of arguments (so the compiler can differentiate between them), you can just add all of them to a single generic interface and call that in lieu of the pointer. Alternatively you could use a select type() construct to selectively associate your pointer with the specific procedure for your type to avoid needing to associate with a generic interface.


    Here is an example of a wrapper procedure to assign the pointer to a specific procedure based on an argument type

    subroutine get_proc_ptr(pp, arg)
      implicit none
      procedure(), pointer, intent(out) :: pp
      class(*), intent(inout) :: arg
    
      select type(arg)
        type is (real(kind=kind(1d0)))
           pp => norm_r8
        type is (real)
           pp => norm_r
        type is (integer)
           pp => norm_i
        type is (complex)
           pp => norm_c
        class default
           pp => null()
      end select
    end subroutine
    

    Which can be made use of like this:

    real(kind=kind(1d0)) :: arg_r8
    procedure(), pointer :: pNorm => null()
    
    arg_r8 = 4.0123456789d30
    
    call get_proc_ptr(pNorm, arg_r8)
    call pNorm(arg_r8)
    

    Here is a complete compilable example:

    module proc
      implicit none
    
      interface norm
        module procedure &
          norm_r8, &
          norm_r,  &
          norm_i,  &
          norm_c
      end interface
    
    contains
    
      subroutine norm_r8(arg)
        implicit none
        real(kind=kind(1d0)), intent(in) :: arg
    
        write (*,*) "real8: ", arg
      end subroutine
    
      subroutine norm_r(arg)
        implicit none
        real, intent(in) :: arg
    
        write (*,*) "real: ", arg
      end subroutine
    
      subroutine norm_i(arg)
        implicit none
        integer, intent(in) :: arg
    
        write (*,*) "integer: ", arg
      end subroutine
    
      subroutine norm_c(arg)
        implicit none
        complex, intent(in) :: arg
    
        write (*,*) "complex: ", arg
      end subroutine
    
      subroutine get_proc_ptr(pp, arg)
        implicit none
        procedure(), pointer, intent(out) :: pp
        class(*), intent(inout) :: arg
    
        select type(arg)
          type is (real(kind=kind(1d0)))
             pp => norm_r8
          type is (real)
             pp => norm_r
          type is (integer)
             pp => norm_i
          type is (complex)
             pp => norm_c
          class default
             pp => null()
        end select
      end subroutine
    
    end module
    
    program test
      use proc
      implicit none
      real(kind=kind(1d0)) :: arg_r8
      real                 :: arg_r
      integer              :: arg_i
      complex              :: arg_c
      procedure(), pointer :: pNorm => null()
    
      arg_r8 = 4.0123456789d30
      arg_r = 12.5
      arg_i = 56
      arg_c = (34,3)
    
      call get_proc_ptr(pNorm, arg_r8)
      call pNorm(arg_r8)
    
      call get_proc_ptr(pNorm, arg_r)
      call pNorm(arg_r)
    
      call get_proc_ptr(pNorm, arg_i)
      call pNorm(arg_i)
    
      call get_proc_ptr(pNorm, arg_c)
      call pNorm(arg_c)
    
    end program
    

    And here is the output of this program:

    $ ./testprocptr 
     real8:    4.0123456788999999E+030
     real:    12.5000000    
     integer:           56
     complex:  (  34.0000000    ,  3.00000000    )