【问题标题】:Procedure pointer to interfaced/overloaded procedure指向接口/重载过程的过程指针
【发布时间】:2014-09-21 12:09:03
【问题描述】:

我正在使用过程重载和接口来在 Fortran 程序中实现某种通用性。
为此,我有一个包含许多程序的模块,所有程序都重复了,以便能够更改变量类型。我在模块的开头也有一系列的接口类型:

    interface norm
      module procedure &
          norm_r8, &
          norm_c
    end interface

现在我的问题是我试图使用过程指针引用norm,就像这样(在不同的模块中):

procedure(), POINTER :: pNorm => NULL()
pNorm => norm

但是,在这种情况下,gfortran 给我一个错误,说我对规范有未定义的引用。如果我指向norm_r8norm_c,没问题。但是由于分配指针的代码部分不知道调用 norm 时将使用的变量的类型,所以我需要指向通用名称!有没有办法指向一个重载的过程?

【问题讨论】:

  • 在 C++ 中也一样——除非将重载函数的地址强制转换为指向具有特定签名的函数的指针,以便编译器可以选择适当的重载,否则无法获得重载函数的地址。这种行为非常有意义,因为除非采用类似于虚拟方法表的机制,否则只能在 C++ 和 Fortran 等静态语言的编译时执行重载解析。

标签: pointers interface fortran generic-programming


【解决方案1】:

据我所知,不允许过程指针指向通用接口。该标准仅提及具有 EXTERNAL 属性的过程、模块过程或某些内在过程可能与过程指针相关联(C1220,ISO/IEC 1539-1:2010)。 Gfortran 还会针对您的案例发出有用的错误消息:

Error: Procedure pointer target 'norm' at (1) must be either an intrinsic, 
       host or use associated, referenced or have the EXTERNAL attribute

您不能关联到接口,而只能关联到过程也是有道理的。接口仅在procedure(INTERFACE) 语句中使用,为它可以指向的过程提供显式接口。

这对你来说不应该是一个阻碍,因为通用接口的目的可以否定你对指针的需求。只要指针将用于的所有潜在调用在类型、种类、等级和参数数量上都是唯一的(因此编译器可以区分它们),您就可以将它们全部添加到单个通用接口并在代替指针。或者,您可以使用select type() 构造来选择性地将您的指针与您的类型的特定过程相关联,以避免需要与通用接口相关联。


这是一个包装过程的示例,用于根据参数类型将指针分配给特定过程

subroutine get_proc_ptr(pp, arg)
  implicit none
  procedure(), pointer, intent(out) :: pp
  class(*), intent(inout) :: arg

  select type(arg)
    type is (real(kind=kind(1d0)))
       pp => norm_r8
    type is (real)
       pp => norm_r
    type is (integer)
       pp => norm_i
    type is (complex)
       pp => norm_c
    class default
       pp => null()
  end select
end subroutine

可以这样使用:

real(kind=kind(1d0)) :: arg_r8
procedure(), pointer :: pNorm => null()

arg_r8 = 4.0123456789d30

call get_proc_ptr(pNorm, arg_r8)
call pNorm(arg_r8)

这是一个完整的可编译示例:

module proc
  implicit none

  interface norm
    module procedure &
      norm_r8, &
      norm_r,  &
      norm_i,  &
      norm_c
  end interface

contains

  subroutine norm_r8(arg)
    implicit none
    real(kind=kind(1d0)), intent(in) :: arg

    write (*,*) "real8: ", arg
  end subroutine

  subroutine norm_r(arg)
    implicit none
    real, intent(in) :: arg

    write (*,*) "real: ", arg
  end subroutine

  subroutine norm_i(arg)
    implicit none
    integer, intent(in) :: arg

    write (*,*) "integer: ", arg
  end subroutine

  subroutine norm_c(arg)
    implicit none
    complex, intent(in) :: arg

    write (*,*) "complex: ", arg
  end subroutine

  subroutine get_proc_ptr(pp, arg)
    implicit none
    procedure(), pointer, intent(out) :: pp
    class(*), intent(inout) :: arg

    select type(arg)
      type is (real(kind=kind(1d0)))
         pp => norm_r8
      type is (real)
         pp => norm_r
      type is (integer)
         pp => norm_i
      type is (complex)
         pp => norm_c
      class default
         pp => null()
    end select
  end subroutine

end module

program test
  use proc
  implicit none
  real(kind=kind(1d0)) :: arg_r8
  real                 :: arg_r
  integer              :: arg_i
  complex              :: arg_c
  procedure(), pointer :: pNorm => null()

  arg_r8 = 4.0123456789d30
  arg_r = 12.5
  arg_i = 56
  arg_c = (34,3)

  call get_proc_ptr(pNorm, arg_r8)
  call pNorm(arg_r8)

  call get_proc_ptr(pNorm, arg_r)
  call pNorm(arg_r)

  call get_proc_ptr(pNorm, arg_i)
  call pNorm(arg_i)

  call get_proc_ptr(pNorm, arg_c)
  call pNorm(arg_c)

end program

这是这个程序的输出:

$ ./testprocptr 
 real8:    4.0123456788999999E+030
 real:    12.5000000    
 integer:           56
 complex:  (  34.0000000    ,  3.00000000    )

【讨论】:

  • 是的,像sincos 这样的通用内在函数甚至存在危险。他们也不允许!必须为正确的种类或包装器使用特定名称。
  • 谢谢!不幸的是,通用接口并没有否定我对指针的需求:我的想法是我有一堆目标函数(每个目标函数都以不同的类型重复 3 次),我希望用户能够选择其中一个目标功能独立于类型。所以基本上泛型接口和指针不用于实现相同的泛型:泛型接口用于泛型 wrt 变量类型,泛型指针 wrt 用户选择的目标函数......
  • @VladimirF 你知道如何用包装器做到这一点吗?我不确定我明白你的意思。
  • @EtiennePellegrini 我已经编辑了一个包装器示例。享受吧。
  • @casey 我从未见过以前使用过的 CLASS(*) 声明。这是某种类型的通用编程吗?我可以像这样声明一个 norm 子例程,而不是通过 norm 和 get_proc_ptr 例程的“GENERIC”接口:subroutine norm(arg) implicit none CLASS(*) :: arg write (*,*), arg end subroutine 因此我不需要规范函数的重载?与重载方法相比,这对性能有影响吗?我是泛型编程的新手,我认为在 Fortran 中实现它的方法是重载。
【解决方案2】:

如果我理解得很好,你想同时实现两件事。 首先,您想使用多态性让编译器根据您是否有不同的类型、等级、参数数量等来调用正确的例程。 其次,您想使用过程指针在具有相同接口的不同过程之间进行切换。

我也试过了。我没有设法设置一个指向接口的指针,但我设法用指针创建了一个接口。

如果你有这样的模块

module some_module

  ! This is the abstract interface for procedure pointers.
  interface
    subroutine shape_1_interface(arg)
      implicit none
      real, intent(in) :: arg
    end subroutine shape_1_interface
    subroutine shape_2_interface(arg)
      implicit none
      integer, intent(in) :: arg
    end subroutine shape_2_interface
  end interface

contains

  subroutine routine_shape_1_implementation_1(arg)
    implicit none
    real, intent(in) :: arg
    write(*,*) "Arg is real",arg
    write(*,*) "Implementation 1"
  end subroutine routine_shape_1_implementation_1

  subroutine routine_shape_2_implementation_1(arg)
    implicit none
    integer, intent(in) :: arg
    write(*,*) "Arg is int",arg
    write(*,*) "Implementation 1"
  end subroutine routine_shape_2_implementation_1

  subroutine routine_shape_1_implementation_2(arg)
    implicit none
    real, intent(in) :: arg
    write(*,*) "Arg is real",arg
    write(*,*) "Implementation 2"
  end subroutine routine_shape_1_implementation_2

  subroutine routine_shape_2_implementation_2(arg)
    implicit none
    integer, intent(in) :: arg
    write(*,*) "Arg is int",arg
    write(*,*) "Implementation 2"
  end subroutine routine_shape_2_implementation_2

  subroutine routine_shape_1_implementation_3(arg)
    implicit none
    real, intent(in) :: arg
    write(*,*) "Arg is real",arg
    write(*,*) "Implementation 3"
  end subroutine routine_shape_1_implementation_3

  subroutine routine_shape_2_implementation_3(arg)
    implicit none
    integer, intent(in) :: arg
    write(*,*) "Arg is int",arg
    write(*,*) "Implementation 3"
  end subroutine routine_shape_2_implementation_3

end module some_module

然后你可以在你的主程序中做:

program main

  use some_module

  implicit none

  procedure(shape_1_interface), pointer :: routine_shape_1
  procedure(shape_2_interface), pointer :: routine_shape_2

  interface routine
    procedure routine_shape_1
    procedure routine_shape_2
  end interface routine

  routine_shape_1 => routine_shape_1_implementation_1
  routine_shape_2 => routine_shape_2_implementation_1

  call routine(4)

  routine_shape_1 => routine_shape_1_implementation_2
  routine_shape_2 => routine_shape_2_implementation_2

  call routine(4.0)

end program main

遗憾的是,当您想将指针设置为不同的实现时,您必须对所有形状都这样做,但好处是您只需调用“例程”即可自动获得所需的功能。

这是输出:

Arg is int           4
Implementation 1
Arg is real   4.00000000000000     
Implementation 2

【讨论】:

    猜你喜欢
    • 2022-01-15
    • 1970-01-01
    • 1970-01-01
    • 2014-02-16
    • 2013-02-19
    • 1970-01-01
    • 1970-01-01
    • 2018-11-15
    • 1970-01-01
    相关资源
    最近更新 更多