Hi, I’m trying to refactor a code meant to enable users adding their own implementations. Not replacing a generic but actually adding to a list of procedures. I have come up with an idea but I’m not too happy with the initialization procedure, and pondering about possible efficiency issues so I would like to ask for any advice if this could be done in a better way:
Here a minimal working example:
code
! this should be in a mod_head.f90
module mod_head
! This module would be unique within the program and its objective is to group the needed
! functionalities and variables for the dynamic procedures creation
implicit none
type :: user_procedure
procedure(abs_sub), pointer :: tsub => null()
end type
abstract interface
subroutine abs_sub(self,x)
import :: user_procedure
class(user_procedure) :: self
real(8), intent(in) :: x
end subroutine
end interface
type(user_procedure), allocatable :: head_procedures(:)
contains
subroutine add_procedure(list,newsze)
type(user_procedure), allocatable :: list(:)
type(user_procedure), allocatable :: tempo(:)
integer, intent(inout) :: newsze
integer :: i
!-------------------------------------------------
if(.not.allocated(list)) then
newsze = 1
allocate( list( newsze ) )
else
newsze = size( list ) + 1
allocate( tempo( newsze ) )
tempo(1:newsze-1) = list(1:newsze-1)
call move_alloc( from=tempo , to=list )
end if
end subroutine add_procedure
end module mod_head
! this should be in a mod_user1.f90. This is the file the user should create
module mod_user1
! this is an example of what a stand alone user implementation could look like.
use mod_head
implicit none
contains
subroutine sub_user( self , x )
class(user_procedure) :: self
real(8), intent(in) :: x
!----------------------------
! actual implementation
print *, x
end subroutine
end module
! this should be in a mod_user2.f90 This is another user file
module mod_user2
! this is an example of what a stand alone user implementation could look like.
use mod_head
implicit none
contains
subroutine sub_user( self , x )
class(user_procedure) :: self
real(8), intent(in) :: x
!----------------------------
! actual implementation
print *, x**2
end subroutine
end module
program main
use mod_head
use mod_user1, only: sub1 => sub_user
use mod_user2, only: sub2 => sub_user
implicit none
integer :: i, sze
real(8) :: x
!-----------------------
call add_procedure(head_procedures,sze)
head_procedures(sze)%tsub => sub1
call add_procedure(head_procedures,sze)
head_procedures(sze)%tsub => sub2
x = 2.d0
do i = 1, size(head_procedures)
call head_procedures(i)%tsub(x)
end do
end program main
This enables me to create an array of procedures dynamically, but since I had to define a procedure to reallocate the list and point to the new implementation on each user module I’m also obliged to do
use mod_user1, only: sub1 => sub_user
use mod_user2, only: sub2 => sub_user
...
call add_procedure(head_procedures,sze)
head_procedures(sze)%tsub => sub1
call add_procedure(head_procedures,sze)
head_procedures(sze)%tsub => sub2
somewhere in the main program.
In my actual code I already have several hundred procedures, so I would rather avoid having to explicitly write a call to this procedures also, or at least parametrize it. Ideally I would preferer to initialize the list of procedures at compile time with available user module files (Ex: I should be able to compile with only mod_user1.f90 if the other file is not present). Or at least to call the initialization at run-time at the very beginning of the program.
I’m thinking about using macros to get the job done but I’m not sure how to pass the name of found files, lets say with CMake, to some macro that shall proceed.
Any suggestions? Thank you in advance