如何实现元素类型可变的Fortran数组?优化现有实现方案
Fortran可变类型数组实现的优化方案探讨
背景与现有实现
我此前通过工厂方法实现了元素类型可变的数组,简化版代码如下:
module multiarray use iso_fortran_env, only: real64 implicit none integer, parameter :: dp = real64 ! 抽象list类型 type, abstract :: list contains procedure(in), deferred, pass(self) :: addval ! 添加变量 procedure(out), deferred, pass(self) :: getval ! 提取变量 end type abstract interface subroutine in(self,val) import :: list implicit none class(list), intent(inout) :: self class(*), intent(in) :: val end subroutine subroutine out(self,val) import :: list implicit none class(list), intent(in) :: self class(*), intent(out) :: val end subroutine end interface ! real(dp)类型扩展 type, extends(list) :: real_num real(dp) :: var contains procedure, pass(self) :: addval => addreal procedure, pass(self) :: getval => getreal end type ! 字符型、逻辑型等类似扩展 ... ! 包装器类型 type wrapper class(list), ALLOCATABLE :: arg end type contains ! real(dp)相关子程序 subroutine addreal(self,val) implicit none class(real_num), intent(inout) :: self class(*), intent(in) :: val select type(val) type is (real(dp)) self%var = val end select end subroutine subroutine getreal(self,val) implicit none class(real_num), intent(in) :: self class(*), intent(out) :: val select type(val) type is (real(dp)) val = self%var end select end subroutine ! 字符型、逻辑型等类似子程序 ... ! 工厂方法 function get_list(val) result(pt) class(*), intent(in) :: val class(list), allocatable :: pt select type(val) type is (real(dp)) allocate(real_num :: pt) type is (character(*)) ... end select end function end module
使用时通过wrapper类型构建数组:
type(wrapper) :: A(5) real(dp) :: x A(1)%arg = get_list(90.d0) ! 将A(1)设为real类型 call A(1)%arg%addval(90.d0) ! A(1)是值为90的real类型变量 call A(1)%arg%getval(x) ! 将A(1)的值赋给x供其他地方使用
问题
由于抽象接口中val被定义为泛型class(*)类型,我必须在addreal这类子程序中用select type来匹配参数。想请教:
- 能否在抽象接口内嵌套定义接口,从而省去
addreal中的select type(因为明确知道这类子程序的输入是real类型)? - 有没有更简便的方式实现这种元素类型可变的数组?
解答
关于抽象接口嵌套的可能性
Fortran的抽象接口不支持嵌套定义,没法通过嵌套接口直接规避select type。但可以通过给抽象类型添加泛型绑定+特定类型专用接口的方式,实现类型匹配的简化:
给抽象类型list扩展泛型接口,同时定义不同类型的专用抽象子程序:
type, abstract :: list contains generic :: addval => addval_generic, addval_real, addval_char generic :: getval => getval_generic, getval_real, getval_char procedure(in_generic), deferred, pass(self) :: addval_generic procedure(out_generic), deferred, pass(self) :: getval_generic procedure(in_real), deferred, pass(self) :: addval_real procedure(out_real), deferred, pass(self) :: getval_real procedure(in_char), deferred, pass(self) :: addval_char procedure(out_char), deferred, pass(self) :: getval_char end type abstract interface ! 原泛型版本 subroutine in_generic(self,val) import :: list implicit none class(list), intent(inout) :: self class(*), intent(in) :: val end subroutine subroutine out_generic(self,val) import :: list implicit none class(list), intent(in) :: self class(*), intent(out) :: val end subroutine ! real类型专用版本 subroutine in_real(self,val) import :: list, dp implicit none class(list), intent(inout) :: self real(dp), intent(in) :: val end subroutine subroutine out_real(self,val) import :: list, dp implicit none class(list), intent(in) :: self real(dp), intent(out) :: val end subroutine ! 字符类型专用版本 subroutine in_char(self,val) import :: list implicit none class(list), intent(inout) :: self character(*), intent(in) :: val end subroutine subroutine out_char(self,val) import :: list implicit none class(list), intent(in) :: self character(*), intent(out) :: val end subroutine end interface
在real_num类型中实现专用版本时,就无需select type:
type, extends(list) :: real_num real(dp) :: var contains procedure, pass(self) :: addval_generic => addreal_generic procedure, pass(self) :: getval_generic => getreal_generic procedure, pass(self) :: addval_real => addreal procedure, pass(self) :: getval_real => getreal procedure, pass(self) :: addval_char => addreal_char_err procedure, pass(self) :: getval_char => getreal_char_err end type contains subroutine addreal(self,val) implicit none class(real_num), intent(inout) :: self real(dp), intent(in) :: val self%var = val ! 直接赋值,无需类型匹配 end subroutine subroutine getreal(self,val) implicit none class(real_num), intent(in) :: self real(dp), intent(out) :: val val = self%var ! 直接赋值 end subroutine ! 泛型版本可调用专用版本或做错误处理 subroutine addreal_generic(self,val) implicit none class(real_num), intent(inout) :: self class(*), intent(in) :: val select type(val) type is (real(dp)) call self%addval(val) class default error stop "addreal_generic: 类型不匹配" end select end subroutine ! 类型不匹配的错误处理 subroutine addreal_char_err(self,val) implicit none class(real_num), intent(inout) :: self character(*), intent(in) :: val error stop "addreal_char_err: 无法向real_num添加字符类型值" end subroutine
调用时编译器会自动匹配对应类型的专用接口,无需手动处理类型判断:
call A(1)%arg%addval(90.d0) ! 直接匹配addval_real call A(1)%arg%getval(x) ! 直接匹配getval_real
更简便的可变类型数组实现方式
如果需求仅为存储不同类型元素并提取,可考虑以下两种简洁方案:
1. 直接使用class(*)可分配数组
无需额外抽象类型和包装器,直接用泛型可分配数组存储:
class(*), allocatable :: A(5) real(dp) :: x allocate(real(dp) :: A(1)) A(1) = 90.d0 select type(val => A(1)) type is (real(dp)) x = val end select
缺点是每次访问元素都需要select type,但代码结构极简,适合简单场景。
2. 利用Fortran 2018参数化类型
若编译器支持Fortran 2018,可通过参数化类型实现类型安全的容器:
type :: container(T) type(T) :: val end type type(container(real(dp))) :: real_arr(5) type(container(character(20))) :: char_arr(5)
注意这种方式只能存储同类型元素,若需要混合类型,仍需结合多态或class(*)。
内容的提问来源于stack exchange,提问作者Al007
相关产品推荐
相关产品推荐

