Fortran 90/95如何让泛型编程与内置类型配合使用

7

我希望编写一些可以处理不同类型的程序。我打算使用flibs中描述的"include"方法,具体内容请参见这里这里。下面是一个简单的例子。

  ! -------------------------------------------------------------- ! 
  module data_type

  type ivalue
  integer :: v
  end type

  type rvalue
  real(8) :: v
  end type

  end module data_type
  ! -------------------------------------------------------------- ! 
  module imod

  use data_type, only: T => ivalue 

  include "template.f90"

  end module imod
  ! -------------------------------------------------------------- ! 
  module rmod

  use data_type, only: T => rvalue 

  include "template.f90"

  end module rmod
  ! -------------------------------------------------------------- ! 
  module mod

  use imod, only:
 &     ivalue => T,
 &     iprintme => printme

  use rmod, only:
 &     rvalue => T,
 &     rprintme => printme

  private
  public :: ivalue, rvalue
  public :: printme

  interface printme
  module procedure iprintme
  module procedure rprintme
  end interface printme

  end module mod
  ! -------------------------------------------------------------- !
  program hello

  use mod

  implicit none

  type(ivalue) :: iv
  type(rvalue) :: rv

  iv%v=42
  rv%v=3.14

  call printme(iv)
  call printme(rv)      

  end program hello

使用包含的文件:
  contains

  subroutine printme(a)

  implicit none

  type(T) :: a

  print *,a

  end subroutine printme

令我困扰的是它似乎只适用于派生类型,而不适用于内在类型。如果模块mod的用户想要在简单整数上使用printme例程,那么对于他来说将其封装在ivalue类型中并且无法执行是非常烦人的:

integer :: a=42
call printme(a)

有没有办法将这种方法扩展到内在类型,或者使用严格的f90/f95进行操作的另一种方法(我不想使用“传输”方法,因为它会复制数据)?
谢谢!

使用CPP预处理器的#include,而不是Fortran的include。 - Vladimir F Героям слава
3个回答

8

您可以在所有主要的Fortran编译器中使用C预处理器(CPP)。通常有一个标志来调用它(-cpp在gfortran中),或者如果文件后缀包含大写F(.F90, .F),则会自动调用它。预处理器允许使用宏更强大地包含源。

module imod

  use data_type, only: ivalue 

#define T type(ivalue)
#include "template.f90"
#undef T

end module imod


module intmod

#define T integer
#include "template.f90"
#undef T

end module intmod

以及 template.f90

contains

subroutine printme(a)

  implicit none

  T :: a

  print *,a

end subroutine printme

这不是严格的f90/f95,但它使用编译器中包含的预处理器生成另一个(严格的f95)源文件,并自动编译它而不是包含宏的原始源文件。
然后编译很简单。
gfortran -cpp main.f90

--编辑--

对于非信徒,如果您想看到使用此代码的真实示例,请查看https://github.com/LadaF/fortran-list(免责声明:这是我的代码)。您可以在那里使用参数化链接列表:

长度为20的字符字符串列表:

module str_list

#define TYPEPARAM character(20)

#include "list-inc-def.f90"
contains
#include "list-inc-proc.f90"
#undef TYPEPARAM
end module

整数列表
module int_list

#define TYPEPARAM integer

#include "list-inc-def.f90"
contains
#include "list-inc-proc.f90"
#undef TYPEPARAM
end module

一些派生类型的列表

module new_type_list
  use, new_type_module, only: new_type

#define TYPEPARAM type(newtype)

#include "list-inc-def.f90"
contains
#include "list-inc-proc.f90"
#undef TYPEPARAM
end module

如果ivalue是内置类型,根据问题,您需要使用F2008来获取结果类型声明语法。 - IanH
不,真的不是这样,我的编译器不支持这个f2008特性,我非常清楚。看到区别了吗,我使用'T ::'而不是'type(T) ::',而且也没有重命名在使用中。 - Vladimir F Героям слава
啊,是的 - 在定义中我漏掉了那个。 - IanH

1
你可以使用隐式类型。但要确保洗手 - 因为这会导致与隐式类型相关的常见错误。请考虑替换你的模块 imod。
module imod
  use data_type    ! oops - I forgot this.
  implicit type(itype) (q)
contains
  include 'template.f90'
end module imod

我已将包含语句移至包含模块中,这样您就可以拥有多个模板化的包含文件。
然后,在包含的文件中,有一个过程看起来像这样:
 ! Assume anything starting with q is the type to be templated.
 subroutine printme(q_arg)
   print *, q_arg
 end subroutine printme

如果你想为内在类型模板打印“printme”,那么你只需相应地更改父模块中的隐式语句。
这是值得商榷的,但也有一种观点认为可以使用模块重命名功能来为内置类型引入新的名称。如果是这样的话,并且你有一个F2008编译器(所以我们不讨论严格的F95),那么你现在的方法仍然能够工作 - 使用一个中间模块来允许将内置整数类型重命名为一个名为T的名称。
但是,这会以某种方式使大多数(或全部?)我使用过的编译器混淆。考虑到这一点,再加上有争议的合法性,再加上它需要F2008,这不是一个真正的解决方案。

0

另一种实现这个目标的方法是使用Python预处理Fortran源代码。在我正在处理的代码中,我们使用这个Python脚本来预处理Fortran模板。例如,这用于编写MPI函数的接口,就像下面这个Fortran代码片段:

module mod_mpi_grid
  implicit none

@python ftypes=["integer(2)","integer(4)","integer(8)","real(4)","real(8)","complex(4)","complex(8)","logical"]
@python fsuffixes=["_i2","_i4","_i8","_f","_d","_c","_z","_l"]
@python fsizeof=["2","4","8","4","8","8","16","2"]
@python fmpitypes=["MPI_INTEGER2","MPI_INTEGER","MPI_INTEGER8","MPI_REAL","MPI_DOUBLE_PRECISION","MPI_COMPLEX","MPI_DOUBLE_COMPLEX","MPI_LOGICAL"]
@python ntypes=8

  interface mpi_grid_send
@template begin
@template variable fsuffix
@python for i in range(ntypes): fsuffix=fsuffixes[i];
    module procedure mpi_grid_send#fsuffix
@template end
  end interface

contains

:

@template begin
@template variable fsuffix
@template variable ftype
@template variable fmpitype
@python for i in range(ntypes): fsuffix=fsuffixes[i]; ftype=ftypes[i]; fmpitype=fmpitypes[i];
  subroutine mpi_grid_send#fsuffix(val,n,dims,dest,tag)
    use mpi
    implicit none

    ! arguments
    #ftype, intent(in) :: val
    integer, intent(in) :: n
    integer, dimension(:), intent(in) :: dims
    integer, dimension(:), intent(in) :: dest
    integer, intent(in) :: tag

    ! local variables
    integer :: comm, dest_rank, req, ierr
    integer :: idims(0:ndmax)

    idims = convert_dims_to_internal(dims)
    comm = mpi_grid_get_comm_internal(idims)
    dest_rank = mpi_grid_rank_internal(comm,idims(0),dest)
    call mpi_isend(val,n,#fmpitype,dest_rank,tag,comm,req,ierr)

    return
  end subroutine mpi_grid_send#fsuffix
@template end

:

end module mod_mpi_grid

然后,在构建时,Makefile 调用以下行将模板预处理为 Fortran 源代码文件以进行编译:

python ftemplate.py < src/addons/mod_mpi_grid.tpl > src/addons/mod_mpi_grid.f90

在子程序中,所有的#ftype实例都被替换为内置类型integer(2)integer(4)、... logical。因此,这种方法与内置类型完美地配合使用!
需要通过MPI发送数据的代码的其他部分只需要use mod_mpi_grid和通用子程序call mpi_grid_send( ... )即可。

那么,我们可以写_另一个_预处理器步骤来填充内在类型参数列表吗? - francescalus
1
顺便提一下,还有 FYPP https://github.com/aradi/fypp 我们应该有一个展示它的答案,但我更希望它是由实际使用它的人编写的。 - Vladimir F Героям слава
@francescalus 这就是预处理器的内嵌! - wyphan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接