在Fortran的现代版本中,是否有可能将类型参数传递给子程序并使用它来“变换”这种变量?例如,在下面的代码中,我试图在打印之前将默认整数转换为16位整数。
program mwe
! Could use iso_fortran_env definition of int16, but I am stuck with
! old versions of ifort and gfortran.
! use, intrinsic :: iso_fortran_env, only : int16
implicit none
! 16-bit (short) integer kind.
integer, parameter :: int16 = selected_int_kind(15)
call convert_print(123, int16)
contains
subroutine convert_print(i, ikind)
implicit none
integer, intent(in) :: i
integer, intent(in) :: ikind
print*, int(i, ikind)
end subroutine convert_print
end program mwe
使用此示例代码,英特尔Fortran编译器会抱怨
mwe.f(24):错误#6238:此上下文中需要整数常量表达式。 [IKIND]
...
mwe.f(24):错误#6683:类型参数必须是编译时常量[IKIND]
和gfortran抱怨
'kind'在'(1)中固有的'int'参数必须是常数
在这种情况下,使用print*, int(i, int16)
代替print*, int(i, ikind)
当然可以正常工作。但是,如果convert_print
在未定义int16
的模块中定义,那么这将是无用的。
有没有办法将种类参数作为常量传递给子程序?
答案 0 :(得分:5)
我有同样的问题。我发现非常不方便不允许将类型数据类型作为参数传递给过程。在我的情况下,我正在编写一个子程序,只是从文件中读取一个矩阵,并获取我想要的数据类型的对象。我必须编写四个不同的子程序:ReadMatrix_int8(...),ReadMatrix_int16(...),ReadMatrix_int32(...)和ReadMatrix_int64(...),它们只是一行不同的相同代码:
integer(kind=xxxx), allocatable, intent(out) :: matrix(:,:)
只编写一个子程序并将xxxx作为参数传递是有意义的。如果我找到任何解决方案,我会告诉你。但是我担心没有比编写四个子程序更好的解决方案,然后编写一个接口来创建一个通用程序,如:
interface ReadMatrix
module procedure ReadMatrix_int8
module procedure ReadMatrix_int16
module procedure ReadMatrix_int32
module procedure ReadMatrix_int64
end interface
答案 1 :(得分:3)
据我所知,Fortran 2003标准(PDF, 4.5 MB)明确禁止我尝试做的事情:
5.1.2.10 PARAMETER属性
除非先前已在同一声明中定义,在先前声明中定义,或通过使用或主机关联可访问,否则不得引用命名常量。
因此,似乎我需要为我希望进行的每次转换定义一个函数,例如:
subroutine print_byte(i)
implicit none
integer, intent(in) :: i
print*, int(i, int8)
end subroutine print_byte
subroutine print_short(i)
implicit none
integer, intent(in) :: i
print*, int(i, int16)
end subroutine print_short
subroutine print_long(i)
implicit none
integer, intent(in) :: i
print*, int(i, int32)
end subroutine print_long
显然,上述所有内容都必须重载才能接受不同类型的输入参数。这似乎需要做很多工作才能避免传递常数,所以如果有人有更好的解决方案,我很想看到它。
答案 2 :(得分:2)
这位英特尔专家给出了解释和优雅的解决方案。我无法更好地解释它。 A full cite follows:
“有一天,当我在当地杂货店的过道上徘徊时,一位女士向我招手,问我是否想”尝试一些进口的巧克力?“桌子上整齐排列着Lindt的包裹, Toblerone,还有... Ghiradelli?我问这位女士是否加利福尼亚已脱离联盟,因为Ghiradelli尽管有意大利名字,却来自旧金山。我想从新罕布什尔州的角度来看,加利福尼亚也可能是另一个这个国家,就像着名的Saul Steinberg 1976年为“纽约客”报道的那样,“从第九大道看世界观”。(我已被警告说我的博客没有足够的任意链接 - 这应该暂存一段时间。)
同样地,在Fortran中(我打赌你想知道我什么时候会这样做),有些事情可能会如此接近,但却显得那么遥远。不久之前,我正在为英特尔Visual Fortran编写一个新模块,为Win32 Process Status API提供声明。这将包含类型和常量的声明以及API例程的接口块,其中一些接受新类型的参数。自然倾向于写下这样的东西:
MODULE psapi
TYPE sometype
some component
END TYPE sometype
INTERFACE
FUNCTION newroutine (arg)
INTEGER :: newroutine
TYPE (sometype) :: arg
END FUNCTION newroutine
END INTERFACE
END MODULE psapi
如果你做了并编译了它,你会得到一个错误,类型sometype在arg的声明中是未定义的。 “什么?这不是未宣布的,我可以在同一个模块中看到它!”嗯,是的,不。是的,它在模块中声明,可以在模块中的任何地方使用,除了..除了接口块!
问题是接口块是“进入外部例程的窗口” - 它们基本上复制了您在实际外部例程中看到的声明,假设该例程是用Fortran编写的。因此,他们不会在封闭范围单元(在这种情况下为模块)中“主持”任何符号。
在Fortran 90和Fortran 95中,典型的解决方案是创建一个单独的模块,比如“psapi_types”,包含要使用的所有类型和常量,然后在每个函数中添加一个USE语句就像在Fortran编写的假设外部例程中一样。 (如果它是用Fortran编写的,那么医生会用湿卡打一下你的手腕,然后告诉你把例程作为一个模块程序,然后你就不用担心这个废话了。)所以你最终会得到一些东西像这样:
MODULE psapi
USE psapi_types ! This is for the benefit of users of module psapi
INTERFACE
FUNCTION newroutine (arg)
USE psapi_types
INTEGER :: newroutine
TYPE (sometype) :: arg
...
使用英特尔Visual Fortran的人知道实际上有一个巨大的模块IFWINTY用于此目的,包含其他Win32 API的所有类型和常量。它既凌乱又不雅,但这就是你必须要做的。到现在为止......
Fortran 2003试图在这种令人遗憾的情况下恢复一些优雅,但为了保持与旧资源的兼容性,它不能只声明接口块参与主机关联。相反,创建了一个新的语句IMPORT。 IMPORT只允许出现在接口块中,它告诉编译器导入在主机范围内可见的名称。
IMPORT位于任何USE语句之后,但在接口主体中的任何IMPLICIT语句之前(FUNCTION或SUBROUTINE声明)。 IMPORT可以有一个可选的import-name-list,就像USE一样。如果没有一个,主机中可访问的所有实体都会在界面体内可见。使用列表,只有命名实体可见。
使用IMPORT,我的PSAPI模块看起来像第一个示例,并进行了以下更改:
...
FUNCTION newroutine (arg)
IMPORT
INTEGER :: newroutine
TYPE(sometype) :: arg
...
如果我愿意,我可以使用:
IMPORT :: sometype
说我只想导入一个名字。很好,整洁,所有在一个模块!
“但你为什么要告诉我这个?”,你可能会问,“这是Fortran 2003的一个功能,英特尔Fortran还没有完成所有的Fortran 2003。”确实如此,但我们不断向编译器添加越来越多的F2003功能,IMPORT在8月份推出了它!因此,如果您保持合理的最新状态,您现在可以根据自己的内容进行导入,并为您的类型和常量消除单独模块的混乱。
如果您想了解其他可用的F2003好东西,请查看每次更新的发行说明。每个问题都包含受支持的F2003功能的完整列表。收集他们所有!“