【问题标题】:Overload for different types and their baseclass不同类型及其基类的重载
【发布时间】:2023-03-17 21:26:01
【问题描述】:

假设我有一个抽象基类Shape_t,派生类型Rectangle_tCircle_t。 两种派生类型都有一个通用函数get_area,我想为类重载它,以便获得以下接口(Julianesque 表示法):

get_area(type(Circle_t) :: C)
get_area(type(Rectangle_t) :: R)
! The following leads to ambiguous interfaces
get_area(class(Shape_t) :: S)

不幸的是,当我尝试这个时,我得到了一个“模棱两可的界面”错误。 由此我有三个问题:

  1. 我想要实现的目标有什么概念上的错误吗?由于变量被显式声明为多态 (class(...)),编译器总是可以选择最具体的接口并回退到多态接口。所以我没有看到歧义。

  2. 如果问题 1 的答案是:“没有概念歧义”。标准中是否有计划对此进行更改?

  3. 以下代码(其中引入了用于动态多态性的dyn_get_area)是一个可靠的解决方法吗?请注意,我想尽可能长时间地坚持静态多态性,即只要具体的 Shape 在编译时是已知的。

module shapes_mod
    implicit none
    private
    public :: Shape_t, Rectangle_t, Circle_t, PI, get_area, dyn_get_area

    real, parameter :: PI = atan(1.0) * 4.0

    type, abstract :: Shape_t
    end type

    type, extends(Shape_t) :: Circle_t
        real :: r = 0.0
    end type

    type, extends(Shape_t) :: Rectangle_t
        real :: a = 0.0, b = 0.0
    end type

    interface get_area
        module procedure get_area_Rectangle_t, get_area_Circle_t
    end interface

    contains

    pure function get_area_Circle_t(C) result(res)
        type(Circle_t), intent(in) :: C
        real :: res
        res = C%r**2 * PI
    end function

    pure function get_area_Rectangle_t(R) result(res)
        type(Rectangle_t), intent(in) :: R
        real :: res
        res = R%a * R%b
    end function

    pure function dyn_get_area(S) result(res)
        class(Shape_t), intent(in) :: S
        real :: res
        select type(S)
        type is(Rectangle_t)
            res = get_area(S)
        type is(Circle_t)
            res = get_area(S)
        end select
    end function

end module


program test_polymorphic_and_static_overload
    use shapes_mod, only: Shape_t, Rectangle_t, Circle_t, get_area, dyn_get_area
    implicit none
    class(Shape_t), allocatable :: random_shape
    type(Circle_t) :: circle
    type(Rectangle_t) :: rectangle
    real :: p

    circle = Circle_t(1.0)
    rectangle = Rectangle_t(1.0, 2.0)

    call random_number(p)
    if (p < 0.5) then
        random_shape = circle
    else
        random_shape = rectangle
    end if

    write(*, *) get_area(circle)
    write(*, *) get_area(rectangle)
    write(*, *) dyn_get_area(random_shape)
end program

【问题讨论】:

标签: fortran polymorphism overloading


【解决方案1】:

Fortran 用于从通用接口中选择特定过程的规则,以及通用接口中的特定过程必须如何区分的规则,旨在易于学习和应用。为了避免需要任何描述如何选择“最佳匹配”/“最具体”的规则,在可能不明确的情况下,语言规则是这样的:最多可以有一个非基本过程,最多可以有一个在任何范围单元中匹配的基本过程(仅在非元素特定不匹配时才考虑元素细节)。

您提议的程序违反了这些语言规则。

我还没有看到任何现实的建议来改变规则,从而失去“易于学习和应用”的设计意图。

在每个类型中放置一个延迟绑定,该绑定指定一个实现 get_area 相关计算的函数。从 get_shape 函数转发到该绑定(或将对 get_shape 的引用更改为对绑定的引用)。避免使用 SELECT TYPE 实现特定于类型的行为 - 应使用绑定来实现特定于类型的行为。

【讨论】:

  • 是否可以补充一下,解释性说明明确指出歧义规则的目标是确保可以在执行开始之前选择特定程序?这部分是禁止“最具体”的动机? (并且还表明设计更改是最不可能的。)
  • 可能取决于定义,但我会考虑“重载”,对于编译语言,总是在声明的参数类型(编译时概念)上完成,并且总是可以在编译时实现。面对明显的歧义,其他编译语言实现了重载的编译时解析(单参数情况并不是问题 - 多个参数使事情变得有趣) - 所以 Fortran 显然也可以,但有语言的缺点必要的消歧规则的复杂性成本。
  • “最具体的声明类型”,我同意注释没有帮助。简单的规则,可以。 (如果你是的话,很高兴删除 cmets。)
  • 我意识到,延迟绑定方法对我的示例有一个明显的缺点。 AFAIK,即使类型在编译时已知,您也会有方法解析的运行时开销。如果您可以在可能的情况下实现静态多态性,并且只在必要时使用动态多态性,我会很高兴听到您如何做到这一点。
  • 实现细节有所不同,但通常 SELECT TYPE 比构建多态对象的描述符和随后的动态调度的开销要慢,并且如果对象不是多态的并且您直接引用绑定,那么就没有动态调度。如果您真的想为每种类型重载 get_area 标识符,请从泛型中提取特定的类(shape_t)(只需给它一个不同的过程名称)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-12
  • 2017-11-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多