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.