Search code examples
fortrangfortransubroutine

How to pass subroutine names as arguments in Fortran classes


In How to pass subroutine names as arguments in Fortran? we learned how to pass subroutine names as arguments in Fortran. How can we do this inside a class structure?

The ensuing code produces the following compilation error using GNU Fortran (GCC) 5.1.0:

gfortran  -Wall -Wextra -Wconversion -Og -pedantic -fcheck=bounds -fmax-errors=5 class_pass.f08 
myClass.f08:44:30:

                 class ( test ), target :: me
                              1
Error: Derived type ‘test’ at (1) is being used before it is defined
myClass.f08:9:21:

             procedure, public :: action => action_sub
                     1
Error: Non-polymorphic passed-object dummy argument of ‘action_sub’ at (1)
myClass.f08:40:36:

         class ( test ), target :: me
                                    1
Error: CLASS variable ‘me’ at (1) must be dummy, allocatable or pointer
(null):0: confused by earlier errors, bailing out

The main routine follows. It includes a routine check used as a diagnostic.

include 'myClass.f08'
program class_pass

    use myClass
    implicit none

    type ( test ) :: myTest

        call myTest % check ()
        call myTest % action ( square_sub )

end program class_pass

The module:

module myClass

    implicit none

    type     :: test
        real :: x, y
        contains
            private
            procedure, public :: action => action_sub
            procedure, public :: square => square_sub
            procedure, public :: double => double_sub
            procedure, public :: check  => check_sub
    end type test

    private :: action_sub
    private :: square_sub
    private :: double_sub
    private :: check_sub

contains

    subroutine square_sub ( me )
        class ( test ), target :: me
            me % y = me % x ** 2
    end subroutine square_sub

    subroutine double_sub ( me )
        class ( test ), target :: me
            me % y = me % x * 2
    end subroutine double_sub

    subroutine check_sub ( me )
        class ( test ), target :: me
            me % x = 5.0
            call double_sub ( me )
            print *, 'x = ', me % x, ', y = ', me % y
    end subroutine check_sub

    subroutine action_sub ( sub )
        class ( test ), target :: me

        interface mySub
            subroutine sub ( me )
                class ( test ), target :: me
            end subroutine sub
        end interface mySub

            call sub ( me )
            print *, 'x = ', me % x, ', y = ', me % y

    end subroutine action_sub

end module myClass

Many thanks to @Vladimir F for the original solution and tips.


Solution

  • Your errors are due to missing parameters in your procedure action_sub and a few other minor things. This procedure is bound to your derived type via

    procedure, public :: action => action_sub
    

    and by default the polymorphic class variable is passed as the first argument to action_sub. You have correctly accounted for this in your other type bound procedures, but are missing it in this procedure. You also need to import the derived type in the interface block within action_sub in order to use the type there. This modified version of just that procedure allows your module to compile properly:

    subroutine action_sub ( me, sub )
        class ( test ), target :: me
    
        interface mySub
            subroutine sub ( me )
                import test
                class ( test ), target :: me
            end subroutine sub
        end interface mySub
    
            call sub ( me )
            print *, 'x = ', me % x, ', y = ', me % y
    
    end subroutine action_sub
    

    next, in your main program where you do:

    call myTest % action ( square_sub )
    

    in order to reference square_sub here you need to make the procedure public in your module. Once you remove the private attribute from the module procedure your code compiles and runs:

     x =    5.00000000     , y =    10.0000000
     x =    5.00000000     , y =    25.0000000