Fortranで未使用仮引数に関する警告を黙らせる

# 概要
手続きの仮引数をその手続き内で使用しない場合，コンパイラが警告を出すことがあります．
コンパイルオプション等で警告を抑制せず，手続き内でそれらの仮引数を参照する方法を紹介します．

# 環境

- Windows 10
- gfortran 8.1.0

# 未使用仮引数の警告
手続きの仮引数をその手続き内で使用しないことがあります．

Fortranでは仮引数に`optional`属性を付与することで，当該の引数の省略を可能にします．この`optional`引数は，様々な場面で活用しますが，その一つが，手続きのインタフェースを記述する場合です．

例えば，差分法を用いて数値シミュレーションを行う場合，その過程で1階微分を計算することがあります．物理的な境界条件に応じて，境界での1階微分の計算方法が変化します．

- 片側差分

```math
f'_i = \frac{-3f_i+4f_{i+1}-f_{i+2}}{2\Delta x}
```
```math
f'_i = \frac{3f_i-4f_{i-1}+f_{i-2}}{2\Delta x}
```
- 値を規定

```math
f'_i = \bar{f'_i}
```

- 変化量を既定

```math
f'_i = f'_{i+1} - \Delta x\bar{f''_i}
```
```math
f'_i = f'_{i-1} + \Delta x\bar{f''_i}
```

このとき，境界条件を計算する手続きのインタフェースを決めておき，境界条件をコールバック関数として渡すことで，実装の省略や分岐の低減が可能になります．

```Fortran
    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`な仮引数を渡していないからと手続き内で参照しないでいると，コンパイラが未使用の仮引数に対して警告を出します．

```console
Warning: Unused dummy argument 'boundary_value' at (1) [-Wunused-dummy-argument]
```

取り扱う物理量や派生量の数が増えると，この警告は増えていくことになります．

# 未使用仮引数の警告の回避
上記の問題に対して，未使用仮引数に関する警告を無効化すればよいのかもしれませんが，それは本当に対策が必要な状況を検出できなくなるので，悪手だと思われます．

そこで，stackoverflowに掲載されていた[対策](https://stackoverflow.com/a/37503935)を紹介し，それを少し改善する方法についても紹介します．

## stackoverflowに掲載されていた対策
stackoverflowにおいて，unused dummy argumentの警告を抑制するのに，C/C++のように，`(void)var;`のようなトリックはFortranに無いのか？という[質問](https://stackoverflow.com/questions/37500015/a-portable-way-to-suppress-an-unused-dummy-argument-warning-in-fortran/37503935#37503935)があり，二つほど回答がありました．

一つは，マクロを利用して`associate`構文を埋め込む方法です．

```Fortran
#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`文の実行を回避できます．


```Fortran
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)`<br>`intent(inout)`|`arg /= 0`|
|論理型|`intent(in)`<br>`intent(inout)`|`arg`|
|文字型|`intent(in)`<br>`intent(inout)`|`len(arg) /= 0`|
|全て|`optional`|`present(arg)`|
|全て|`pointer`|`associated(arg)`|
|全て|`allocatable`|`allocated(arg)`|
|ユーザ定義型|`intent(in)`<br>`intent(inout)`|`same_type_as(arg, arg)`|
|全て|`intent(out)`|そもそもの設計が悪い可能性があるので，設計を見直す|
|コールバック関数|`optional`でない|そもそもの設計が悪い可能性があるので，設計を見直す|

また，ここまでやるくらいなら仮引数未使用の警告は無視する，というのも一つの回答だと思います．