16

在 fortran 中,我们可以定义默认参数。但是,如果不存在可选参数,则也不能设置它。当将参数用作具有默认值的关键字参数时,这会导致尴尬的结构,例如

PROGRAM PDEFAULT 

  CALL SUB
  CALL SUB(3)

CONTAINS 
  SUBROUTINE SUB(VAL)
    INTEGER, OPTIONAL :: VAL
    INTEGER :: AVAL ! short for "actual val"

    IF(PRESENT(VAL)) THEN
       AVAL = VAL
    ELSE 
       AVAL = -1   ! default value 
    END IF

    WRITE(*,'("AVAL is ", I0)') AVAL
  END SUBROUTINE SUB

END PROGRAM PDEFAULT

就我个人而言,经常会遇到不小心输入VAL而不是 的问题AVAL,即接口中的变量名与代码中使用的初始化值之间的脱节会引入运行时错误——更不用说这种初始化方式相当冗长。

有没有更优雅的方式使用带有默认值的可选参数?

示例写类似的东西会感觉更自然

IF(NOT(PRESENT(VAL))) VAL = -1 

因为它避免了VALvsAVAL混淆。但它是无效的,大概是因为 Fortran 通过引用传递参数,因此如果语句VAL中不存在,则CALL没有内存与之关联VAL并且VAL = -1会导致段错误。

4

6 回答 6

12

你描述的情况相当好。我没有其他方法知道,这是符合标准的。具有类似名称的局部变量的模式是人们经常使用的。另一种选择是if (present()) else到处放,但这很尴尬。

关键是它们是可选参数,而不是默认参数。Fortran 没有默认参数。可能会更好,但这不是委员会成员在 80 年代准备 Fortran 90 时所选择的。

于 2016-06-09T11:26:50.230 回答
7

在对此进行研究的同时,我发现您实际上可以使用OPTIONALandVALUE属性执行类似于建议示例的操作(至少对于 gfortran,不确定不同的编译器如何处理它)。例如:

PROGRAM PDEFAULT 

  CALL SUB
  CALL SUB(3)

CONTAINS 
  SUBROUTINE SUB(VAL)
    INTEGER, OPTIONAL,VALUE :: VAL

    IF(.NOT. PRESENT(VAL)) VAL = -1 ! default value

    WRITE(*,'("VAL is ", I0)') VAL
  END SUBROUTINE SUB

END PROGRAM PDEFAULT

这是在 gfortran 4.9 版中实现的。这是参数传递约定文档中的相关解释:

对于 OPTIONAL 虚拟参数,不存在的参数由 NULL 指针表示,但具有 VALUE 属性的 INTEGER、LOGICAL、REAL 和 COMPLEX 类型的标量虚拟参数除外。对于那些,隐藏的布尔参数 (logical(kind=C_bool),value) 用于指示参数是否存在。

我还发现这个讨论作为历史背景很有趣。

也许更有知识的人可能会评论这样做是否是一个坏主意(除了依赖于编译器),但至少从表面上看,这似乎是一个不错的解决方法。

请注意,此行为不是 Fortran 标准的一部分,它取决于给定编译器的实现。例如,示例代码在使用 ifort(版本 16.0.2)时会出现段错误。

于 2016-10-19T07:04:26.940 回答
1

Fortran 标准库 ( https://github.com/fortran-lang/stdlib ) 提供了一个名为optval的函数,用于stdlib_logger例如:

subroutine add_log_file( self, filename, unit, action, position, status, stat )
    ...
    character(*), intent(in), optional :: action
    ...
    character(16)  :: aaction
    ...
    aaction = optval(action, 'write')
    ...
end subroutine add_log_file

所以他们表示“实际”值的方式是前置的a.

恕我直言,我喜欢带有附加的选项_,因为可选值在调用签名中被直观地标记为这样。

于 2020-10-01T22:52:37.353 回答
1

虽然我当然不主张在大多数情况下这样做(实际上在某些情况下您不能这样做),但有时可能会使用接口为具有不同必需参数的多个例程提供单个入口点,而不是使用可选参数。例如,您的代码可以写成

MODULE subs
  implicit none
  public :: sub

  interface sub
    module procedure sub_default
    module procedure sub_arg
  end interface
 contains
  SUBROUTINE SUB_arg(VAL)
    INTEGER :: VAL
    WRITE(*,'("VAL is ", I0)') VAL
  END SUBROUTINE SUB_arg

  SUBROUTINE SUB_default
     integer, parameter :: default = 3
     CALL SUB_arg(default)
  END SUBROUTINE SUB_default
END MODULE SUBS

PROGRAM test
   use subs, only: sub
   call sub
   call sub(5)
END PROGRAM TEST

同样,我不推荐这种方法,但我认为无论如何我都应该将它作为一种替代方式来提供看起来像默认值的东西。

于 2016-06-09T18:39:21.790 回答
1

另一种可能性是使用关联块,它将局部变量名称与与可选参数同名的变量相关联,例如。

SUBROUTINE SUB(VAL)
INTEGER, OPTIONAL :: VAL
INTEGER :: AVAL ! short for "actual val"

IF (PRESENT(VAL)) THEN
    AVAL = VAL
ELSE 
    AVAL = -1   ! default value 
END IF

ASSOCIATE (VAL => AVAL)
    WRITE(*,'("VAL is ", I0)') VAL
END ASSOCIATE

END SUBROUTINE SUB

不理想,但允许您在参数和例程主体中使用相同的变量名。想到我为应对可选参数缺少默认值而编写的大量不整洁的代码,我不寒而栗——在 F202X 上滚动。

于 2019-07-05T13:26:57.270 回答
0

我希望 Fortran 支持流行的语法,比如

subroutine mysub( x, val = -1 )
integer, optional :: val

或者更多的 Fortran 风格

subroutine mysub( x, val )
integer, optional :: val = -1     !! not SAVE attribute intended

但这似乎不受支持(截至 2016 年)。所以一些解决方法需要由用户方完成......

就我而言,经过反复试验,我决定在可选的虚拟参数上附加一个下划线,所以做一些类似 (*)

subroutine mysub( x, val_)
integer, optional :: val_
integer val

其他人似乎喜欢相反的模式(例如,虚拟变量 => sep、局部变量 => sep_,请参见StringiFor中的split())。如这一行所示,设置默认值的最短方法是

val = -1 ; if (present(val_)) val = val_

但是因为即使这一行有点冗长,我通常会定义一个宏

#define optval(x,opt,val) x = val; if (present(opt)) x = opt

在一个通用的头文件中并将其用作

subroutine mysub( x, val_, eps_ )
    integer :: x
    integer, optional :: val_
    real, optional :: eps_
    integer  val
    real     eps
    optval( val, val_, -1 )
    optval( eps, eps_, 1.0e-5 )    

    print *, "x=", x, "val=", val, "eps=", eps
endsubroutine

...
call mysub( 100 )
call mysub( 100, val_= 3 )
call mysub( 100, val_= 3, eps_= 1.0e-8 )

然而,我相信这仍然远非优雅,只不过是努力使其不易出错(通过在子例程的主体中使用所需的变量名)。


非常“大”的子例程的另一个解决方法可能是传递包含所有剩余关键字参数的派生类型。例如,

#define getkey(T) type(T), optional :: key_; type(T) key; if (present(key_)) key = key_

module mymod
    implicit none

    type mysub_k
        integer  :: val = -1
        real     :: eps = 1.0e-3
    endtype
contains

subroutine mysub( x, seed_, key_ )
    integer :: x
    integer, optional :: seed_
    integer :: seed
    getkey(mysub_k)   !! for all the remaining keyword arguments
    optval( seed, seed_, 100 )    

    print *, x, seed, key% val, key% eps
endsubroutine

endmodule

program main
    use mymod, key => mysub_k

    call mysub( 10 )
    call mysub( 20, key_= key( val = 3 ) )
    call mysub( 30, seed_=200, key_= key( eps = 1.0e-8 ) )  ! ugly...
endprogram

这可能有点接近幕后某些动态语言所做的事情,但这又远非上述形式的优雅......


(*) 我知道使用 CPP 宏通常被认为是丑陋的,但 IMO 这取决于它们的使用方式;如果它们仅限于 Fortran 语法的有限扩展,我觉得使用是合理的(因为 Fortran 中没有元编程工具);另一方面,应该避免定义依赖于程序的常量或分支。另外,我想使用 Python 等来制作更灵活的预处理器(例如PreForM.pyfypp等)会更强大,例如,允许类似的语法subroutine sub( val = -1 )

于 2016-06-12T19:21:17.477 回答