如何在属于同一个多态变量的两个元素之间进行交换?

时间:2019-06-26 16:16:55

标签: fortran gfortran intel-fortran

当需要互换两个多态元素中的值时,最佳方法是什么? (使用标准的fortran 2008)。

我正在发送示例(请尝试不要修改类型变量)。

我在Windows中使用Intel编译器v.19和gfortran 8.1的问题有所不同。

这是一个完整的例子。查看定义了交换过程的子例程。当前是激活在GFortran中可用的版本,但是intel编译器出现错误。如果您对此部分发表评论而对ifort的行取消注释,则适用于intel,而不适用于gfortran。...

    Program Check
   implicit none

   !> Type definitions
   Type :: Refl_Type
      integer,dimension(:), allocatable :: H            
      integer                           :: Mult  =0     
   End Type Refl_Type

   Type :: RefList_Type
      integer                                     :: Nref
      class(refl_Type), dimension(:), allocatable :: Reflections
   end Type RefList_Type

   Type(RefList_Type)            :: List
   Type(Refl_Type), dimension(3) :: Refl_Ini

   !> Variables 
   integer :: i

   !> Init
   Refl_Ini(1)%H=[1, 0, 0]; Refl_Ini(1)%Mult=1
   Refl_Ini(2)%H=[0, 2, 0]; Refl_Ini(2)%Mult=2
   Refl_Ini(3)%H=[0, 0, 3]; Refl_Ini(3)%Mult=3

   List%Nref=3
   List%Reflections=Refl_Ini

   !> Print Step:1
   do i=1, List%Nref
      print '(i3,2x,3i4,2x,i3)', i,List%Reflections(i)%H, List%Reflections(i)%Mult
   end do  
   print*,' '
   print*,' '

   !> Swap
   call Swap_Elements_List(List, 1, 3)

   !> Print Step:2
   do i=1, List%Nref
      print '(i3,2x,3i4,2x,i3)', i,List%Reflections(i)%H, List%Reflections(i)%Mult
   end do

Contains

   Subroutine Swap_Elements_List(List, i, j)
      !---- Argument ----!
      type (RefList_Type), intent(in out) :: List
      integer,             intent(in)     :: i,j

      !---- Local Variables ----!
      class(Refl_Type), allocatable :: tmp

      !> IFort
      !tmp=List%reflections(i)
      !List%reflections(i)=List%reflections(j)
      !List%reflections(j)=tmp

      !> Gfortran
      associate(t1 => list%reflections(i), t2 => list%reflections(j), tt => tmp)
         tt=t1
         t1=t2
         t2=tt
      end associate  
   End Subroutine Swap_Elements_List

End Program Check

有什么建议吗?

2 个答案:

答案 0 :(得分:1)

使用gfortran-8.2编译原始代码会得到

    test.f90:34:6:
           List%reflections(i)=List%reflections(j) !!<---
          1
    Error: Nonallocatable variable must not be polymorphic in 
           intrinsic assignment at (1) - check that there is a 
           matching specific subroutine for '=' operator

我认为这是因为List % reflections(i)并不是单独地allocatable(即使List % reflections本身也可以作为统一类型的数组进行分配)。例如,在本Q/A page中似乎对此进行了详细讨论,其中提出了两种替代方法:(A)使编译器确信所有元素都属于同一类型;或(B)使用(数组)容器。


如果我们使用“容器”方法,我认为我们可以使用move_alloc()来交换两个多态对象(不知道动态类型)。例如,原始代码的修改版本可能是

program main
   implicit none

   type :: Refl_t
      integer, allocatable :: H(:)
   endtype

   type, extends(Refl_t) :: ExtRefl_t
      real :: foo
   endtype

   type :: RefList_t
      class(Refl_t), allocatable :: refl
   endtype

   type(RefList_t) :: list( 3 )

   call init()

   print *, "Before:"
   call output()

   call swap( 1, 2 )

   print *, "After:"
   call output()

contains

   subroutine swap( i, j )
       integer, intent(in) :: i, j
       class(Refl_t), allocatable :: tmp

       call move_alloc( from= list( i )% refl, to= tmp             )
       call move_alloc( from= list( j )% refl, to= list( i )% refl )
       call move_alloc( from= tmp,             to= list( j )% refl )
   end
   subroutine init()
       integer i
       do i = 1, 3
           allocate( ExtRefl_t :: list( i ) % refl )

           select type( x => list( i ) % refl )
               type is ( ExtRefl_t )
                   x % H   = [ i, i * 10 ]
                   x % foo = i * 100
           endselect
       enddo
   end
   subroutine output()
       integer i
       do i = 1, 3
           select type( x => list( i ) % refl )
               type is ( ExtRefl_t )
                   print *, "i = ", i, " : H = ", x % H, " foo = ", x % foo
           endselect
       enddo
   end
end program

结果(gfortran-8.2):

 Before:
 i =            1  : H =            1          10  foo =    100.000000    
 i =            2  : H =            2          20  foo =    200.000000    
 i =            3  : H =            3          30  foo =    300.000000    
 After:
 i =            1  : H =            2          20  foo =    200.000000    
 i =            2  : H =            1          10  foo =    100.000000    
 i =            3  : H =            3          30  foo =    300.000000 

我认为我们也可以对上述swap()例程使用多态赋值,例如:

   subroutine swap( i, j )
       integer, intent(in) :: i, j
       class(Refl_t), allocatable :: tmp

       tmp              = list( i ) % refl
       list( i ) % refl = list( j ) % refl
       list( j ) % refl = tmp
   end

这可以使用gfortran-8.2进行编译,但是会给出奇怪的结果……(可能是编译器错误?)。我想像GCC-9或Intel Fortran这样的较新的编译器可能会产生预期的结果。


另一方面,如果我们使用多态数组,则可能需要显式使用select type来交换两个元素。 (但我希望有另一种方法...)然后代码可能像这样:

program main
   implicit none

   type :: Refl_t
      integer, allocatable :: H(:)
   endtype

   type, extends(Refl_t) :: ExtRefl_t
      real :: foo
   endtype

   class(Refl_t), allocatable :: refls( : )

   allocate( ExtRefl_t :: refls( 3 ) )
   call init()

   print *, "Before:"
   call output()

   call swap( 1, 2 )

   print *, "After:"
   call output()

contains

   subroutine swap( i, j )
       integer, intent(in) :: i, j

       selecttype ( refls )
           type is ( ExtRefl_t )
               block
                 type(ExtRefl_t) :: tmp

                 tmp        = refls( i )   !<-- assignment of concrete type
                 refls( i ) = refls( j )
                 refls( j ) = tmp
               endblock
           class default
               stop
       endselect
   end
   subroutine init()
       integer i

       select type( refls )
           type is ( ExtRefl_t )
               do i = 1, 3
                   refls( i ) % H   = [ i, i * 10 ]
                   refls( i ) % foo = i * 100
               enddo
       endselect
   end
   subroutine output()
       integer i
       select type( refls )
           type is ( ExtRefl_t )
               do i = 1, 3
                   print *, "i = ", i, " : H = ", refls( i ) % H, &
                            " foo = ", refls( i ) % foo
               enddo
       endselect
   end
end program

(结果与上面相同。)

答案 1 :(得分:0)

roygvib的回答很好地概括了这个问题。如果要在用户类型已知或已知的类型很少的用户代码中执行此分配,则可以只使用select type类型保护程序来保护分配。

真正的问题发生在编写通用代码时,而无需知道用户的派生类型。因此,它可能无法访问可能的用户定义的分配。我建议使用回调过程的可能解决方案。基本上,用户定义一个分配或交换过程,然后由库代码调用。

subroutine sub_that_needs_assignments(array, assign)
  class(*) :: array
  interface
    subroutne assign(out, in)
    end subroutine
  end interface


  call assign(array(i), array(i+1))

  !or you can even assign a new elemnt from somewhere else
  ! possibly  protect by same_type_as()
end subroutine

使用用户代码

   subroutine assign_my_type(out, in)
     class(*), ... :: out
     class(*), ... :: in

     select type (out)
       type is (my_type)
         select type (in)   ! not always necessary
           type is (in)
             out = in

         end select
      end select
      !add appropriate error checking

  end subroutine