Search code examples
fortran

Overloading function in Fortran and polymorphic derived data types


I have a program dealing with calculations for an engine. There are multiple limiters that need to be considered (pressure ratio, temperature, ect.). These are organized from users perspective in groups, with some parameters common to all groups and some not.

Because during the run time I need to work with these limiters depending on the requirements, potentially changing them during various calculation steps it would make sense to organize these in an array of polymorphic elements, depending on what each limiter group needs. In principle it works, but not quite how I want to.

I wrote a small program to test different method shown below:

Here is the module with derived types etc.

module ArrayTest
    
interface init_limiter
module procedure :: initGroup1, initGroup2
end interface

type :: base
contains
procedure, pass :: setup => idontwanttodothis
procedure, pass :: print_param
end type base

type, extends(base) :: Group1
    real :: p1
contains
procedure, pass :: init => initGroup1
procedure, pass :: print_param => printGroup1
end type Group1

type, extends(base) :: Group2
    integer :: p1
    real :: rDummy
contains
procedure, pass :: init => initGroup2
procedure, pass :: print_param => printGroup2
end type Group2

type ArrElem
    integer :: a, b, c
    class(base), allocatable :: param
end type ArrElem

type(ArrElem), dimension(5) :: T1, T2

contains

subroutine idontwanttodothis(self, iDummy, rDummy)
class(base) :: self
integer, optional :: iDummy
real, optional :: rDummy

select type (self)
type is(group1); call self.init(rDummy)
type is(group2); call self.init(iDummy,rDummy)
end select
end subroutine idontwanttodothis

subroutine print_param(self)
class(base) :: self

select type(self)
type is(group1); call self.print_param()
type is(group2); call self.print_param()
class default; write(*,'(A)') 'Type:: Unknown'
end select
end subroutine print_param

pure subroutine initGroup1(self, x)
class(Group1), intent(inout) :: self
real, intent(in) :: x
self.p1 = x
end subroutine initGroup1

pure subroutine initGroup2(self, x, y)
class(Group2), intent(inout) :: self
integer, intent(in) :: x
real, intent(in) :: y
self.p1 = x
self.rDummy = y
end subroutine initGroup2

subroutine printGroup1(self)
class(Group1) :: self
write(*,'(A,F5.2)') 'Type:: Group1 ',self.p1
end subroutine printGroup1

subroutine printGroup2(self)
class(Group2) :: self
write(*,'(A,I2,F5.2)') 'Type:: Group2 ',self.p1, self.rDummy
end subroutine printGroup2

end module ArrayTest

And here is the main program:

program TestAlloc
use ArrayTest

call main()

contains

subroutine main

integer i
type(group1) :: g1Dummy

!Option 1
g1Dummy.p1 = 29
allocate(T1(1).param, source = g1Dummy)

!Option 2
allocate(Group2::T1(2).param)
select type(dummy => T1(2).param)
type is(Group2); call dummy.init(12,8.7)
end select

!Option 3
allocate(Group2::T1(3).param)
call T1(3).param.setup(3, 4.5)

!Option 4
allocate(Group1::T1(4).param)
call init_limiter(T1(4).param, 8.) !this does not work
call init_limiter(g1Dummy, 8.) !this works

T2 = T1

do i=1,5
    if(allocated(T2(i).param)) call T2(i).param.print_param()
end do

return
end subroutine main
end program TestAlloc

Options 1, 2 and 3 work. Option 4 doesn't. Is there any way to make this work? i.e. overload a function call for an allocatable parameter?

p.s. Overriding inherited function through child will work, but that will require both parent and children to have the same interface, which I find inconvenient, might as well use option 3 then.


Solution

  • To my knowledge, there is no way to make this work.

    As far as the compiler is concerned, at compile time T1(4).param is of class(base), and it only becomes type(Group1) at runtime. Since you have not defined init_limiter for class(base), only for class(Group1) and class(Group2), the compiler has no appropriate init_limiter function to call.

    Your init_limiter functions are not polymorphic, they simply share an interface, so the compiler has no way of treating them the same at compile time and calling the correct one at runtime using polymorphism.

    p.s. Overriding inherited function through child will work, but that will require both parent and children to have the same interface, which I find inconvenient, might as well use option 3 then.

    This is essentially the crux of your problem. You want to call a function with a different number of arguments depending on the runtime type of an object. Fortran is not set up to handle this case; the number and type of function arguments must be known at compile time.

    One potential workaround, which you might or might not consider an improvement, is to use the select type construct. This allows you to turn runtime information (the type of T1(4).param) into compile time information (the signature of the function to call). This would look something like

    allocate(Group1::T1(4).param)
    select type(foo => T1(4).param); type is(Group1)
    call init_limiter(foo, 8.)
    end select