配列を手続きの引数として受け取る4通りの方法と配列の上下限を維持する方法

# 概要
配列を形状引継配列として手続き（サブルーチンまたは関数）に渡したときに，配列の上下限を維持する方法をまとめました．

# 環境
- gfortran 8.4.0
- intel fortran 2021.1
- nvfortran 20.11-0

# Fortranの配列の上下限
Fortranでは，配列の上下限を指定できます．下の例では，配列に`allocatable`属性を付けて動的に割り付けられるようにし，`allocate`文で割り付けています．このとき上下限は，第1次元を2から7まで，第2次元を3から5までとしています．

```Fortran
program test
    use, intrinsic :: iso_fortran_env
    implicit none

    integer(int32) :: l1
        !! lower bound of the 1st dimension
    integer(int32) :: l2
        !! lower bound of the 2nd dimension
    integer(int32) :: u1
        !! upper bound of the 1st dimension
    integer(int32) :: u2
        !! upper bound of the 2nd dimension
    real(real64), allocatable :: f(:, :)

    character(*), parameter :: fmt = '(*(g0:,","))'

    l1 = 2; l2 = 3
    u1 = 7; u2 = 5
    allocate (f(l1:u1, l2:u2))
    print '(A)', "print bounds of f in main routine"
    print fmt, lbound(f), ubound(f)
    print '(A)', "---"
end program test
```

`lbound`は配列の下限を返す組込関数，`ubound`は配列の上限を返す組込関数です．これを実行すると，下記のような出力が得られます．

```console
print bounds of f in main routine
2,3,7,5
---
```

# 手続きに配列を渡す方法
Fortranでは，手続きの仮引数として配列を渡す場合，4通りの書き方ができます．

1. 形状明示配列
1. 形状無指定配列
1. 形状引継配列
1. 大きさ引継配列

下のサブルーチンを例に，違いを説明します．

```Fortran
program test
    ! 省略
contains
    subroutine print_bounds(array)
        use, intrinsic :: iso_fortran_env
        implicit none
        real(real64), intent(in) :: array(?) ! ?の書き方によって挙動が変わる

        print fmt, lbound(array), ubound(array)
    end subroutine print_bounds
end program test
```

## 形状明示配列
形状明示配列は，仮引数として渡される配列の**全ての上下限**を明記します．

```Fortran
real(real64), intent(in) :: array(2:7,3:5)
```

```Fortran
program test
    ! 省略

    l1 = 2; l2 = 3
    u1 = 7; u2 = 5
    allocate (f(l1:u1, l2:u2))
    print '(A)', "print bounds of f as array in subroutine"
    call print_bounds(f)
    print '(A)', "---"
end program test
```

実行すると，次のような表示が得られます．

```console
print bounds of f as array in subroutine
2,3,7,5
---
```

明記しているので，メインルーチン内と同じ結果が得られます．しかし，これでは柔軟性に欠けるので，上下限を別途引数として渡すことも行われています．

```Fortran
program test
    ! 省略
    l1 = 2; l2 = 3
    u1 = 7; u2 = 5
    allocate (f(l1:u1, l2:u2))

    print '(A)', "print bounds of f as array in subroutine"
    call print_bounds(f,lbound(f),ubound(f))
    print '(A)', "---"
contains
    subroutine print_bounds(array, lower, upper)
        use, intrinsic :: iso_fortran_env
        implicit none
        integer(int32),intent(in) :: lower(2)
        integer(int32),intent(in) :: upper(2)
        real(real64), intent(in) :: array(lower(1):upper(1),lower(2):upper(2))

        print fmt, lbound(array), ubound(array)
    end subroutine print_bounds
end program test
```

実行結果は同じです．

```console
print bounds of f as array in subroutine
2,3,7,5
---
```

## 形状無指定配列
形状明示配列は，配列の上下限を指定する必要があり，そのための情報も渡す必要がありました．これは面倒なので，配列の次元だけを指定する書き方がFortran 90から追加されました．

```Fortran
real(real64), intent(in) :: array(:,:)
```

これを形状無指定配列と呼びます．形状無指定配列は，配列の上下限の情報を持っていないので，下限が1になり，上限は下限に合わせてシフトされます．

```Fortran
program test
    ! 省略
    l1 = 2; l2 = 3
    u1 = 7; u2 = 5
    allocate (f(l1:u1, l2:u2))

    print '(A)', "print bounds of f as array in subroutine"
    call print_bounds(f)
    print '(A)', "---"
contains
    subroutine print_bounds(array)
        use, intrinsic :: iso_fortran_env
        implicit none
        real(real64), intent(in) :: array(:,:)

        print fmt, lbound(array), ubound(array)
    end subroutine print_bounds
end program test
```

```console
print bounds of f as array in subroutine
1,1,6,3
---
```

第1次元は2~7が1~6に，第2次元は，3~5が1~3になっています．

## 形状引継配列
形状引継配列は，形状明示配列と形状無指定配列の中間で，配列の下限の情報だけを指定します．

```Fortran
real(real64), intent(in) :: array(2:,3:)
```

実用上は，形状明示配列と同様に，配列下限も引数として渡すことで，任意サイズの配列に対応します．

```Fortran
program test
    ! 省略
    l1 = 2; l2 = 3
    u1 = 7; u2 = 5
    allocate (f(l1:u1, l2:u2))

    print '(A)', "print bounds of f as array in subroutine"
    call print_bounds(f,lbound(f))
    print '(A)', "---"
contains
     subroutine print_bounds(array, lower)
        use, intrinsic :: iso_fortran_env
        implicit none
        integer(int32),intent(in) :: lower(2)
        real(real64), intent(in) :: array(lower(1):,lower(2):)

        print fmt, lbound(array), ubound(array)
    end subroutine print_bounds
end program test
```

```console
print bounds of f as array in subroutine
2,3,7,5
---
```

## 大きさ引継配列
大きさ引継配列は，配列の大きさ（要素数）を受け取ります．配列の上下限どころか，次元が異なっていても問題ありません．大きさ引継配列は，最も大きい次元の要素数を`*`とします．それ以外の次元では，要素数か上下限を指定する必要があります．

大きさ引継配列としては，下記の書き方が許されます．

```Fortran
real(real64), intent(in) :: array(2:7,*)  ! 下限を指定し，最も大きい次元（第2次元）の要素数を*とする
real(real64), intent(in) :: array(1:6,*)  ! :6とは書けず，下限が1でも指定が必要．あるいはarray(6,*)と書く．
real(real64), intent(in) :: array(*)      ! 次元数が異なっていてもよい
real(real64), intent(in) :: array(2,3,*)  ! 次元数が異なっていてもよい
```

形状明示配列を利用する際，下限は1と仮定されますが，上限は明記する必要があります．

```Fortran
program test
    ! 省略
    l1 = 2; l2 = 3
    u1 = 7; u2 = 5
    allocate (f(l1:u1, l2:u2))

    print '(A)', "print bounds of f as array in subroutine"
    call print_bounds(f)
    print '(A)', "---"
contains
     subroutine print_bounds(array)
        use, intrinsic :: iso_fortran_env
        implicit none
        real(real64), intent(in) :: array(2:7,*)

        print fmt, lbound(array), ubound(array(:,:5)) ! ubound(array)やubound(array(:,:))とはできない
    end subroutine print_bounds
end program test
```

上記のプログラムでは，`lbound`では下限が1とされので配列名`array`を渡すだけでよいのですが，上限を参照する`ubound`を利用する際は，上限を指定しなければなりません．また，gfortran 8.4.0ではコンパイルできません．

```console
print bounds of f as array in subroutine
2,1,6,5
---
```

## 結局どうするの？
形状無指定配列が最も使いやすいのですが，配列の下限が1になってしまうのが唯一の欠点です．配列下限が1の場合は，問答無用で形状無指定配列を使いましょう．
配列下限が1になってしまう問題を回避するために，配列を成分に持つ派生型を定義し，派生型を使うこともよく行われます．これは値オブジェクトの概念も関係してくるので，今後の主流になるかも知れません．

次点で形状明示配列です．下限を引数として渡す場合，`lbound`を利用すると，配列の各次元の下限値をまとめて配列として返してくれるので，それを引数として手続きに渡すのが楽です．

# 形状無指定配列で上下限を維持する方法
結論から言うと，配列の仮引数に`pointer`属性を付け，実引数となる配列に`target`属性を付けると，手続き内でも上下限が維持されます．
検証できるように，プログラム全体を示しておきます．

```Fortran
program test
    use, intrinsic :: iso_fortran_env
    implicit none

    integer(int32) :: l1
        !! lower bound of the 1st dimension
    integer(int32) :: l2
        !! lower bound of the 2nd dimension
    integer(int32) :: u1
        !! upper bound of the 1st dimension
    integer(int32) :: u2
        !! upper bound of the 2nd dimension

    real(real64), allocatable, target :: f(:, :)

    character(*), parameter :: fmt = '(*(g0:,","))'

    l1 = 2; l2 = 3
    u1 = 7; u2 = 5
    allocate (f(l1:u1, l2:u2))

    print '(A)', "print bounds of f in main routine"
    print fmt, lbound(f), ubound(f)
    print '(A)', "---"

    print '(A)', "print bounds of f as array in subroutine"
    call print_bounds(f)
    print '(A)', "---"

    print '(A)', "print bounds of f as pointer in subroutine "
    call print_pointer_bounds(f)
    print '(A)', "---"

contains
    !| receive an array and print its bounds
    subroutine print_bounds(array)
        use, intrinsic :: iso_fortran_env
        implicit none
        real(real64), intent(in) :: array(:,:)

        print fmt, lbound(array), ubound(array)
    end subroutine print_bounds

    !| receive an array as a pointer and print its bounds
    subroutine print_pointer_bounds(array)
        use, intrinsic :: iso_fortran_env
        implicit none
        real(real64), dimension(:, :), pointer, contiguous, intent(in) :: array

        print fmt, lbound(array), ubound(array)
    end subroutine print_pointer_bounds
end program test
```

`pointer`属性の付け方について補足しておくと，Fortranにはポインタの配列がないので，`ral(real64)`型の2次元配列へのポインタであることを明記するために，`real(real64), dimension(:, :), pointer`と書いています．`real(real64), pointer, contiguous, intent(in) :: array(:, :)`と書くと，ポインタの2次元配列と勘違いしてしまいそうなので，意識的に書き方を変えています[^WIP]．

[^WIP]: https://qiita.com/implicit_none/items/9164c4d35d84e6e77f29でも説明しています．

これを実行すると，以下のように表示されます．

```console
print bounds of f in main routine
2,3,7,5
---
print bounds of f as array in subroutine
1,1,6,3
---
print bounds of f as pointer in subroutine 
2,3,7,5
---
```

`print_bounds`内での表示結果を見ると，配列の下限が1になっています．一方，`print_pointer_bounds`内での表示結果を見ると，配列の下限が維持されていることが判ります．

# まとめ
配列を手続きに渡すときに，形状無指定配列でも上下限を維持する方法を紹介しました．
手続きの定義において，仮引数の配列に`pointer`属性を付け，実引数となる配列に`target`属性を付けるだけです．

しかし，これは万能な方法ではありません．全ての配列に`target`属性を付けるのも億劫ですし，派生型の成分には`target`属性を付与することができません．また，Fortranでは`pointer`属性が非常に曖昧で，仮引数に`pointer`属性が付いていても非ポインタ変数を渡せますし，逆に仮引数に`pointer`属性が付いていなくてもポインタ変数を渡せます．元々ポインタを意識しなくてよかった言語だけに，ポインタが関係すると動作がややこしくなっているように感じます．このあたりはFORTRAN 77との兼ね合いもあるので，根本的な解決は難しいのでしょうか．