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.
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