在Fortran中重载赋值运算符和元素级赋值运算符

3

我正在尝试为自定义类型重载赋值运算符,并希望它能够使用自动分配。我阅读了这个线程,并编写了以下代码:

module overload_op
  implicit none
  PUBLIC ASSIGNMENT(=)

  TYPE, PUBLIC :: my_type
    real :: real_member
  END TYPE my_type

  INTERFACE ASSIGNMENT (=)
    MODULE PROCEDURE assign_my_type_my_type_elem
    MODULE PROCEDURE assign_my_type_my_type
  END INTERFACE

  contains
    ELEMENTAL SUBROUTINE assign_my_type_my_type_elem (var1, var2)
      TYPE(my_type), INTENT(OUT) :: var1
      TYPE(my_type), INTENT(IN) :: var2
      var1%real_member = var2%real_member
    END SUBROUTINE assign_my_type_my_type_elem

    SUBROUTINE assign_my_type_my_type (var1, var2)
      TYPE(my_type), ALLOCATABLE, INTENT(OUT) :: var1(:)
      TYPE(my_type), ALLOCATABLE, INTENT(IN) :: var2(:)
      if (.not.allocated(var1)) allocate(var1(size(var2)))
      ! Call the elemental assignment subroutine for gfortran, fail for Intel
      var1(:) = var2(:)
    END SUBROUTINE assign_my_type_my_type   
end module overload_op

program main

  use overload_op
  implicit none
  TYPE(my_type) :: a(3), b(3)
  TYPE(my_type), allocatable :: c(:), d(:)
  b = a
  allocate(d(3))
  c = d

end program main

在我的理解中,对于可分配数组,代码应该调用assign_my_type_my_type,对于定义形状的数组(例如使用(:)规范的数组)或简单的my_type变量,应调用elemental。

这在我使用gfortran编译器最新版本10.0.1时能够按照我的意愿工作。但是当我尝试使用ifort编译器(intel/2020.1版本)进行编译时,首先会出现以下错误:

error #6437:一个子例程或函数正在递归地调用自身。 [ASSIGN_MY_TYPE_MY_TYPE] var1(:) = var2(:)

当我将代码更改为

call assign_my_type_my_type_elem(var1(:), var2(:))

我在主程序的变量a,b上遇到了没有可分配属性的错误。

一个可分配的虚参只能是与可分配的实参相关联的参数。

所以我的问题是:我的实现是否完全错误,并且使用(:)符号时,我没有调用基本赋值运算符?换句话说,gfortran有bug并且Intel是对的,还是反过来?


在我的理解中,对于可分配数组,代码应该调用assign_my_type_my_type,而对于定义形状的数组,则应该调用elemental函数。您能解释一下为什么这样做吗? - francescalus
无论如何,assign_my_type_my_type 都不能直接通过定义的赋值调用:它的第二个参数是可分配的。 - francescalus
我阅读了您提供的问题,确实非常相关!但是很抱歉,仍有些事情我不理解:1)如果实现不正确,为什么gfortran可以工作?2)除了带有intent(IN)的可分配虚拟参数之外,我的实现哪里出错了?他在他的实现中使用了类,而我没有。我应该使用吗? - Sparonuz
1
我很惊讶编译器没有抱怨这两个接口是模糊的。 - veryreverie
1
@veryreverie,我希望编译器不会抱怨接口是模糊的,因为Fortran的规则很好地定义了应该调用哪个接口(如果有一致的非元素过程,则始终优先选择它)。 - francescalus
显示剩余2条评论
1个回答

1
你的实现有误,但不容易修复。
子程序assign_my_type_my_type定义了当abtype(my_type)的秩为1的数组时定义的赋值a=bassign_my_type_my_type_elem因此从未为秩为1的数组定义定义的赋值:如果没有其他子程序,则元素子程序仅定义赋值。
决定子程序是否定义特定的赋值(Fortran 2018 10.2.1.4),对于可分配参数或非可分配参数没有区别。(正如我在其他地方所说:“没有要求规定定义的赋值需要调用所选的子程序!”)
assign_my_type_my_type内部,赋值为:
var1(:) = var2(:)

尽管var1(:)不可分配,但它仍由assign_my_type_my_type本身定义。如果这种情况发生,这将是递归的(因为实际参数不可分配),但实际上这种情况是不可能发生的。

您试图使用的方法是

call assign_my_type_my_type_elem(var1(:), var2(:))

与定义的赋值相比,使用实际参数确实能够正常指向基本(非递归)子程序。但正如您所指出的那样,在主程序的定义赋值中会失败。

b = a

您尝试使用非可分配参数调用子例程assign_my_type_my_type。您调用此子例程,因为这是定义该类型的秩-1数组分配的子例程,无论是否可调用。
这就导致了为什么这不容易修复:不可能创建一个通用程序,其中两个特定程序仅由其中一个参数的可分配性不同。也不能选择“元素如果非元素不可调用”。
您必须选择支持可分配左侧或非可分配左侧。或者使用“包装器”类型。

assign_my_type_my_type 存在一个容易解决的问题: var2 的可分配属性意味着子程序永远不能通过定义的赋值直接调用。无论如何,在您的用法中,该参数不需要是可分配的,因此请删除该属性。

最后,关于“自动分配”的实现的概念说明。第一个参数 allocatable,intent(out) 左侧始终在赋值之前被取消分配。这与内部赋值的自动分配情况不同。

在内部赋值中,仅当左侧与右侧存在某种方式的不匹配(最常见的是大小)时,左侧才会被取消分配。如果您想要在定义的赋值中获得一致的行为,则需要具有 intent(inout) 并进行各种取消分配测试。


谢谢@francescalus!基本上我想做的事情是无法完成的,因为我试图支持两种类型的lhs。我仍然想知道为什么gfortran可以成功编译和运行... - Sparonuz
1
如果你给编译器一个有问题的实现,它可以自由地创建一些“有效”的东西(通过选择或运气),但你不能依赖它这样做。 - francescalus

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