概要
手続きの仮引数をその手続き内で使用しない場合,コンパイラが警告を出すことがあります.
コンパイルオプション等で警告を抑制せず,手続き内でそれらの仮引数を参照する方法を紹介します.
環境
- Windows 10
- gfortran 8.1.0
未使用仮引数の警告
手続きの仮引数をその手続き内で使用しないことがあります.
Fortranでは仮引数にoptional
属性を付与することで,当該の引数の省略を可能にします.このoptional
引数は,様々な場面で活用しますが,その一つが,手続きのインタフェースを記述する場合です.
例えば,差分法を用いて数値シミュレーションを行う場合,その過程で1階微分を計算することがあります.物理的な境界条件に応じて,境界での1階微分の計算方法が変化します.
- 片側差分
f'_i = \frac{-3f_i+4f_{i+1}-f_{i+2}}{2\Delta x}
f'_i = \frac{3f_i-4f_{i-1}+f_{i-2}}{2\Delta x}
- 値を規定
f'_i = \bar{f'_i}
- 変化量を既定
f'_i = f'_{i+1} - \Delta x\bar{f''_i}
f'_i = f'_{i-1} + \Delta x\bar{f''_i}
このとき,境界条件を計算する手続きのインタフェースを決めておき,境界条件をコールバック関数として渡すことで,実装の省略や分岐の低減が可能になります.
subroutine compute_gradient(var, grad, boundary)
use, intrinsic :: iso_fortran_env
implicit none
interface
subroutine Icompute_gradient_boundary(var, grad, boundary_value, boundary_gradient)
use, intrinsic :: iso_fortran_env
real(real64), intent(in) :: var(:)
real(real64), intent(inout) :: grad(:)
real(real64), intent(in), optional :: boundary_value
real(real64), intent(in), optional :: boundary_gradient
end subroutine Icompute_gradient_boundary
end interface
real(real64), intent(in) :: var(:)
real(real64), intent(inout) :: grad(:)
procedure(Icompute_gradient_boundary) :: boundary
! varからgradを計算する
! 境界条件
call boundary(var, grad)
end subroutine compute_gradient
あり得る条件に対応した処理を実装するには,境界での値や変化量も引数として設けておく必要がありますが,計算式によっては境界での値も変化量も必要としません.このような場合に,境界での値や変化量をoptional
引数とすることで,不要な実引数を用意する必要がなくなります.
しかし,optional
な仮引数を渡していないからと手続き内で参照しないでいると,コンパイラが未使用の仮引数に対して警告を出します.
Warning: Unused dummy argument 'boundary_value' at (1) [-Wunused-dummy-argument]
取り扱う物理量や派生量の数が増えると,この警告は増えていくことになります.
未使用仮引数の警告の回避
上記の問題に対して,未使用仮引数に関する警告を無効化すればよいのかもしれませんが,それは本当に対策が必要な状況を検出できなくなるので,悪手だと思われます.
そこで,stackoverflowに掲載されていた対策を紹介し,それを少し改善する方法についても紹介します.
stackoverflowに掲載されていた対策
stackoverflowにおいて,unused dummy argumentの警告を抑制するのに,C/C++のように,(void)var;
のようなトリックはFortranに無いのか?という質問があり,二つほど回答がありました.
一つは,マクロを利用してassociate
構文を埋め込む方法です.
#define nop(x) associate( x => x ); end associate
もう一つは,if
文で明示的に仮引数を使用するが,条件文が真になった際の実行文にcontinue
を用いる(if (条件式) continue
とする)ことで,コンパイルオプションによってif
文の評価が省略されることを期待する方法です.
条件式は,仮引数の属性に応じて決めるように書かれています.
型 | 属性 | 条件式 |
---|---|---|
数値型 | intent(in) |
arg /= 0 |
論理型 | intent(in) |
arg |
文字型 | intent(in) |
len(arg) /= 0 |
全て | optional |
present(arg) |
全て | pointer |
associated(arg) |
全て | allocatable |
allocated(arg) |
ユーザ定義型 | intent(in) |
成分の型に応じて上記の条件式を使う |
全て | intent(out) |
そもそもの設計が悪い可能性があるので,設計を見直す |
少しの改良
上記のif
文を用いる対策において,ユーザ定義型の成分が全てprivate
の場合は成分を参照できず,if
文の条件式に用いる事ができません.
この問題に関しては,same_type_as
関数を用い,仮引数の型が仮引数自身と同じかを検査する(same_type_as(arg, arg)
)ことで,成分の有無やアクセス指定子にかかわらず,条件式を作る事ができます.
また,コンパイルオプションによってif
文の評価が省略されることを期待していますが,実際にどうなるかは判りません.これについては,if
文を手続きの最後にまとめておき,if
文より前にreturn
で手続きから抜けるようにすれば,省略の有無にかかわらずif
文の実行を回避できます.
type :: user_defined_type
integer(int32), private :: i
end type
interface
function Iproc(arg) result(retval)
use, intrinsic :: iso_fortran_env
real(real64), intent(in) :: arg
real(real64) :: retval
end function
end interface
contains
subroutine subroutine_with_optionalarg(i32, b64, char, str, lgc, proc, opt, ptr, alloc, udt)
use, intrinsic :: iso_fortran_env
implicit none
integer(int32), intent(in) :: i32 ! intent(in)の数値型
real(real64), intent(in) :: b64 ! intent(in)の数値型
character, intent(in) :: char(:) ! intent(in)の文字型
character(*), intent(in) :: str ! intent(in)の文字型
logical, intent(in) :: lgc ! intent(in)の論理型
real(real64), intent(in), optional :: opt ! optional引数
real(real64), pointer, intent(inout) :: ptr ! pointer引数
real(real64), allocatable, intent(inout) :: alloc(:) ! allocatable 引数
type(user_defined_type), intent(in) :: udt ! ユーザ定義派生型
procedure(Iproc) :: proc ! 手続き(コールバック関数)
return
if (i32 /= 0) continue
if (b64 /= 0d0) continue ! 実数の等価性を比較していると警告が出る場合もある
if (len(char) /= 0) continue ! 配列要素数に限らずlenの戻り値は1(長さ1の文字列×要素数という扱い)
if (len(str) /= 0) continue
if (lgc) continue
if (present(opt)) continue
if (associated(ptr)) continue
if (allocated(alloc)) continue
if (same_type_as(udt, udt)) continue
end subroutine subroutine_with_optionalarg
しかし,stackoverflowに載っていた対策でも,本記事での対策でも,コールバック関数については対応できません.コールバック関数がpointer
やoptional
属性を持っている場合はpresented(arg)
やassociated(arg)
で条件式を作れますが,pointer
でもoptional
でもない場合は,手続き内で呼び出さない限り,未使用についての警告は抑制できません.
「コールバック関数を必ず渡さなければならないが,それが渡された手続き内で使われないこともある」という状況は,設計自体がよくないとも考えられます.optional
属性が付けられないか,あるいは他の設計にできないかを検討した方がよいでしょう.
まとめ
未使用の仮引数に対する警告を抑制する方法について紹介し,少しの改良を加えました.
if
文で明示的に仮引数を使用し,条件文が真になった際の実行文にcontinue
を用います.コンパイルオプションによってif
文の評価が省略されることを期待しますが,省略されなくてもよいように,手続き末尾にif
文を配置して,それより前にretrun
で手続きから抜けるようにします.
また,stackoverflowの回答では,intent(inout)
属性に関して何も言及されていませんでしたが,intent(inout)
属性の変数でも,この対策は利用できます.
optional
属性を持つ仮引数であれば,present(arg)
を条件式にすれば全て解決します.
そうでない場合については,条件式を下記表にまとめておきますが,こういう状況に頻繁に陥るのであれば設計の見直しを検討した方がよいかもしれません.
型 | 属性 | 条件式 |
---|---|---|
数値型 |
intent(in) intent(inout)
|
arg /= 0 |
論理型 |
intent(in) intent(inout)
|
arg |
文字型 |
intent(in) intent(inout)
|
len(arg) /= 0 |
全て | optional |
present(arg) |
全て | pointer |
associated(arg) |
全て | allocatable |
allocated(arg) |
ユーザ定義型 |
intent(in) intent(inout)
|
same_type_as(arg, arg) |
全て | intent(out) |
そもそもの設計が悪い可能性があるので,設計を見直す |
コールバック関数 |
optional でない |
そもそもの設計が悪い可能性があるので,設計を見直す |
また,ここまでやるくらいなら仮引数未使用の警告は無視する,というのも一つの回答だと思います.