Search code examples
classfortrangfortransubroutine

Selecting procedures within a class in Fortran


How do we select procedures within classes? The compilation error is:

$ gfortran  -Wall -Wextra -Wconversion -Og -pedantic -fcheck=bounds -fbacktrace -g  -fmax-errors=5 driver.f08 
driver.f08:60:39:

         call sub ( me, myMeasurements )
                                       1
Error: More actual than formal arguments in procedure call at (1)

which occurs in subroutine local_selector_sub (line 48) within module mIntermediates (line 28). The argument sub will be either compute_intermediates_dot or compute_intermediates_sum.

The MWE code driver.f08 tries to select a computation path. Calls to the two choices, compute_intermediates_sum and compute_intermediates_dot both work (lines 93, 94).

The problem is the call to local_selector (line 95) which contains the name of the target computation path. The goal is have different manifestations such as

call myIntermediates % local_selector( compute_intermediates_sum, myMeasurements )

or

call myIntermediates % local_selector( compute_intermediates_dot, myMeasurements )

MWE:

module mMeasurements  !   *   *   *

    implicit none
    integer, parameter :: m = 2

    type :: measurements
        real, dimension ( 1 : m ) :: x = 0.0, y = 0.0, ones = 1.0, residuals = 0.0
    contains
        private
        procedure, public :: load_data
    end type measurements

contains

    subroutine load_data ( me )

        class ( measurements ), target :: me

            me % x ( 1 ) = 1.0 !           load data
            me % x ( 2 ) = 2.0
            me % y ( 1 ) = 15.6
            me % y ( 2 ) = 17.5

    end subroutine load_data

end module mMeasurements

module mIntermediatesDefinitions  !   *   *   *

    use mMeasurements
    implicit none

    type :: intermediates
        real :: sxy = 0.0
    contains
        private
        procedure, public :: compute_intermediates_dot => compute_intermediates_dot_sub
        procedure, public :: compute_intermediates_sum => compute_intermediates_sum_sub
        procedure, public :: local_selector            => local_selector_sub
    end type intermediates

    private :: local_selector_sub
    private :: compute_intermediates_dot_sub
    private :: compute_intermediates_sum_sub

contains

    subroutine local_selector_sub ( me, sub, myMeasurements )  ! problematic routine

        class ( intermediates ), target      :: me
        type ( measurements ), intent ( in ) :: myMeasurements

        interface mySub
            subroutine sub
                use mMeasurements
                import intermediates
            end subroutine sub
        end interface mySub

        call sub ( me, myMeasurements )

    end subroutine local_selector_sub

    subroutine compute_intermediates_dot_sub ( me, myMeasurements )

        class ( intermediates ), target      :: me
        type ( measurements ), intent ( in ) :: myMeasurements

            me % sxy = dot_product ( myMeasurements % x,    myMeasurements % y )

    end subroutine compute_intermediates_dot_sub

    subroutine compute_intermediates_sum_sub ( me, myMeasurements )

        class ( intermediates ), target      :: me
        type ( measurements ), intent ( in ) :: myMeasurements

            me % sxy = sum ( myMeasurements % x * myMeasurements % y )

    end subroutine compute_intermediates_sum_sub

end module mIntermediatesDefinitions

program driver  !   #   #   #

    use mMeasurements
    use mIntermediatesDefinitions

    implicit none
    type ( measurements )  :: myMeasurements
    type ( intermediates ) :: myIntermediates

        call myIntermediates % compute_intermediates_dot ( myMeasurements )
        call myIntermediates % compute_intermediates_sum ( myMeasurements )
        call myIntermediates % local_selector            ( compute_intermediates_dot, myMeasurements )

end program driver

Version: GNU Fortran (GCC) 5.1.0


Solution

  • The dummy procedure sub in subroutine local_selector_sub has interface given by the interface block. That interface

    interface
      subroutine sub
      end subroutine sub
    end interface
    

    (with the redundant use and import and the unused generic name removed) says that sub has no arguments. In the following call you attempt to give it two arguments, and the compiler rightly complains about that.

    To fix that you will need to specify the interface correctly (or rely on an implicit interface). As I note that both options compute_intermediates_dot_sub and compute_intermediates_sum_sub have the same characteristics you could write, for example

    subroutine local_selector_sub ( me, sub, myMeasurements )  ! problematic routine
      class ( intermediates ), target          :: me
      type ( measurements ), intent ( in )     :: myMeasurements
      procedure(compute_intermediates_dot_sub)    sub
    
      call sub ( me, myMeasurements )
    
    end subroutine
    

    (or you could create a neutrally named abstract interface or use an appropriate interface block).

    But that leads to a much more interesting problem:

    call myIntermediates%local_selector(compute_intermediates_dot, myMeasurements)
    

    compute_intermediates_dot is not a subroutine. It is a binding name for the type bound procedure of type intermediates.

    The simplest solution if you wish to pass a subroutine to myIntermediates%local_selector is to make the subroutine compute_intermediates_dot_sub itself accessible/public and pass that. But I'd be tempted to stick with choosing between the other two lines.

    Alternatively, if you really wish to select a subroutine to pass to myIntermediates%local_selector then you could consider having procedure pointer components rather than type-bound procedures.