Search code examples
oopfortrangfortran

Can I overload a type-bound procedure based on the number of arguments?


I'm working on an aerodynamics code (source code here) using good old Fortran. Part of the initialization is reading in a file which contains information on a surface mesh, basically a set of triangular or quadrilateral panels making up the surface. I'd like to describe both quadrilateral and triangular panels using a single type panel. To do that, I've written two initialization functions panel_init_3 and panel_init_4 which will load the data into the type based on how many vertices the panel has. I'd like to bind these to my panel type and overload them based on the number of arguments (i.e. if it gets passed an integer and 3 vertex objects, then panel_init_3 gets called and similarly for 4 vertex objects.

Here is the source code for the type:

module panel_mod

    use linked_list_mod
    use vertex_mod

    implicit none


    type panel
        ! A panel with an arbitrary number of sides

        integer :: N ! Number of sides/vertices
        type(vertex_pointer),dimension(:),allocatable :: vertices
        real,dimension(3) :: n_hat ! Normal vector
        real :: A ! Surface area

        contains

            procedure :: init => panel_init_3, panel_init_4
            procedure :: calc_area => panel_calc_area
            procedure :: calc_normal => panel_calc_area

    end type panel

    
contains


    subroutine panel_init_3(this, v1, v2, v3)
        ! Initializes a 3-panel

        implicit none

        class(panel),intent(inout) :: this
        type(vertex),intent(in),target :: v1, v2, v3

        ! Set number of sides
        this%N = 3

        ! Allocate vertex array
        allocate(this%vertices(this%N))

        ! Store info
        this%vertices(1)%ptr => v1
        this%vertices(2)%ptr => v2
        this%vertices(3)%ptr => v3

        ! Calculate normal vec

        ! Calculate area

    end subroutine panel_init_3


    subroutine panel_init_4(this, v1, v2, v3, v4)
        ! Initializes a panel with 4 sides

        implicit none

        class(panel),intent(inout) :: this
        type(vertex),intent(in),target :: v1, v2, v3, v4
        
        ! Set number of sides
        this%N = 4

        ! Allocate vertex array
        allocate(this%vertices(this%N))

        ! Store info
        this%vertices(1)%ptr => v1
        this%vertices(2)%ptr => v2
        this%vertices(3)%ptr => v3
        this%vertices(4)%ptr => v4

        ! Calculate normal vector

        ! Calculate area

    end subroutine panel_init_4


    subroutine panel_calc_area(this)

        implicit none

        class(panel),intent(inout) :: this

    end subroutine panel_calc_area


    subroutine panel_calc_normal(this)

        implicit none

        class(panel),intent(inout) :: this

    end subroutine panel_calc_normal

    
end module panel_mod

This module compiles fine. But, when it gets used here

...
! Initialize triangular panel
if (N == 3) then
   call panels(i)%init(vertices(i1+1), vertices(i2+1), vertices(i3+1)) ! Need +1 because VTK is 0-indexed

! Initialize quadrilateral panel
else
   call panels(i)%init(vertices(i1+1), vertices(i2+1), vertices(i3+1), vertices(i4+1))
end if
...

I get the compiler message

   70 |                     call panels(i)%init(vertices(i1+1), vertices(i2+1), vertices(i3+1), vertices(i4+1))
      |                                                                                                       1
Error: More actual than formal arguments in procedure call at (1)

Am I reaching beyond the capabilities of Fortran, or is there a way to do this? I realize I could do this all without binding and overloading, but I like how clean it'd be. I am using gfortran 9.3.0.


Solution

  • It is possible, but you need to define init as a generic procedure (binding) with those other two being the specific procedures (bindings)

       contains
    
            procedure :: panel_init_3
            procedure :: panel_init_4
            generic :: init => panel_init_3, panel_init_4
    

    It is quite similar to what is normally done with normal procedures using named interface blocks when you do

    interface generic_init
      procedure specific_init_3
      procedure specific_init_4
    end interface