Template in Fortran?
I have a module that defines three types and and some operations on them.
In a separate module, I want to define an algorithm that operates on either one of these types using the operations defined in the module. The algorithm is the same, regardless of the type. I can overload it, but I was wondering if I can save a lot of typing by defining some sort of template algorithm. What I have in mind is something like class templates in C++.
Thanks
Fortran does not have templates, but you can put the code common to the functions handling different types in an include file as a kludge to simulate templates, as shown in this code:
! file "cumul.inc"
! function cumul(xx) result(yy)
! return in yy(:) the cumulative sum of xx(:)
! type, intent(in) :: xx(:)
! type :: yy(size(xx))
integer :: i,n
yy = 0
n = size(xx)
if (n < 1) return
yy(1) = xx(1)
do i=2,n
yy(i) = yy(i-1) + xx(i)
end do
return
! end function cumul
! end file "cumul.inc"
module foo
implicit none
integer, parameter :: sp = kind(1.0), dp = kind(1.0d0)
interface cumul
module procedure cumul_double,cumul_real,cumul_int
end interface cumul
contains
!
function cumul_double(xx) result(yy)
real(kind=dp), intent(in) :: xx(:)
real(kind=dp) :: yy(size(xx))
include "cumul.inc"
end function cumul_double
!
function cumul_real(xx) result(yy)
real(kind=sp), intent(in) :: xx(:)
real(kind=sp) :: yy(size(xx))
include "cumul.inc"
end function cumul_real
!
function cumul_int(xx) result(yy)
integer, intent(in) :: xx(:)
integer :: yy(size(xx))
include "cumul.inc"
end function cumul_int
end module foo
program xcumul
use foo, only: cumul
print*,cumul([10,20,30])
print*,cumul(sin([10.0,20.0,30.0]))
print*,cumul(sin([10.0d0,20.0d0,30.0d0]))
end program xcumul
! output:
! 10 30 60
! -0.5440211 0.36892414 -0.6191075
! -0.5440211108893698 0.3689241398382579 -0.6191074842546039
The tool mentioned in the paper
Car, David and Michael List (2010). PyF95++: A Templating Capability for the Fortran 95/2003 Language. ACM Fortran Forum 29(1), 2-20.
may interest you. I have not tried it.
My answer is similar to @Fortranner's answer but I usually place the whole implementation inside of an include file. It has the advantage that multiple procedures could be handled by one single include file and it is easier to maintain, e.g. you can see the whole procedure's defintion, etc.
Some preprocessor voodoo (see PASTE
, CONCATHELP
, etc.) is needed but it works.
The same example as given by @Fortranner could be written as follows.
The main file main.f90
:
program main
use cumul_m, only: cumul
implicit none
print *, cumul([1, 2, 3 ]) ! output: 1 3 6
print *, cumul([1.1, 2.2, 3.3 ]) ! output: 1.1 3.3 6.6
print *, cumul([1.1d0, 2.2d0, 3.3d0]) ! output: 1.1 3.3 6.6
end program
The module file cumul.f90
:
module cumul_m
use iso_fortran_env, only: real32, real64
implicit none
private
public cumul
interface cumul
module procedure :: cumul_int, cumul_sp, cumul_dp
end interface
contains
#define T int
#define TT integer
#include "cumul_imp.f90.inc"
#define T sp
#define TT real(real32)
#include "cumul_imp.f90.inc"
#define T dp
#define TT real(real64)
#include "cumul_imp.f90.inc"
end module
The implementation's include file cumul_imp.f90.inc
:
#define PASTE(X) X
#define PASTE2(X) PASTE(X)_
#define CONCATHELP(X, Y) PASTE2(X)Y
#define CONCAT(X, Y) CONCATHELP(X,Y)
#define CUMUL CONCAT(cumul,T)
function CUMUL(x) result(y)
!! the cumulative sum of x
TT, intent(in) :: x(:)
!! array of values
TT :: y(size(x))
!! cumulative sum of x
integer :: i, n
n = size(x)
if (n < 1) return
y(1) = x(1)
do i = 2, n
y(i) = y(i-1) + x(i)
end do
end function
#undef T
#undef TT
#undef PASTE
#undef PASTE2
#undef CONCATHELP
#undef CONCAT
#undef CUMUL
Note that when compiling you need to enable the preprocessor (e.g. -cpp
for gfortran):
$ gfortran -cpp -c cumul.f90
$ gfortran cumul.o main.f90
$ ./a.out
1 3 6
1.10000002 3.30000019 6.60000038
1.1000000000000001 3.3000000000000003 6.5999999999999996
The emerging Fortran Standard Library project uses the fypp pre-processor by @BálintAradi for templating.
For example, you can define the types and kinds for which the subprograms should be generated and loop for all those selected types and kinds in a for
loop:
#:set REAL_KINDS = ["sp", "dp", "qp"]
#:set REAL_TYPES = ["real({})".format(k) for k in REAL_KINDS]
...
#:set RCI_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES + INT_KINDS_TYPES
#:for k1, t1 in RCI_KINDS_TYPES
module procedure trace_${t1[0]}$${k1}$
#:endfor
To include a file with some fypp macros one uses
#:include "macrodefs.fypp"
and then you can use very similar techniques as with other preprocessors like the cpp use in jacks answer and also in other questions and answers on this site (like my https://stackoverflow.com/a/24070364/721644).
The above for loop is then very handy for instantiation of your templates for existing types.