type-bound procedure
Fortran 2003 以降、オブジェクト指向(OO: Object Oriented)に対応するため派生型の中にサブルーチンや関数を置けるようになりました。これを type に結び付けられたサブルーチン・関数という意味で、type-bound procedure と呼びます。
またこれに伴って、Fortran 2003 以降では関数ポインタ(より正確には procedure pointer)が導入されました。派生型の要素/成分として関数ポインタを持つことができます。そうして、この関数ポインタに結び付けたサブルーチン・関数は、オブジェクト指向式の contains 以下にサブルーチン・関数名を列挙した場合と同じように、派生型のインスタンスから呼び出すことができます。
この関数ポインタ方式は、サブルーチン・関数の自己引数 (this, self, me etc) として class が取れない場合でも適用することができます。たとえば、sequence 並びの派生型や、parametrized derived type のようなものです。 extend 出来ない派生型を考えれば目安になるのではないかと思います。
以下で sequence を使った例を見てみます。
sequence を使った type
派生型の要素成分にとった変数は、一般的には型宣言の順番とは別にコンパイラが適当に並べ替えてメモリー上に確保されます。大抵ハードウェアを効率的に利用するための制約上から4バイトや8バイトの区切りのいい番地から変数が始まるように調整がされます。
しかし、例えばフォーマットの定まったバイナリファイルの I/O などを行う時は、派生型の構造がバイナリファイルの構造と完全に一致するようにしておくと、I/O を要素毎に読み書きせずとも派生型全体を一括で扱うことが出来便利です。このような場合に対応するため、派生型を定義する時に sequence を宣言しておくと、型宣言の順番通りに忠実にメモリー上に要素成分の変数が確保されるようになります。
ただ、この sequence 宣言をすると、派生型を extends していわゆる継承をすることが出来なくなります。それは、extends して付け足した変数をどう並べるかが判然としないためだと思います。
実例1
storage_size 組み込み関数は fortran 2008 で導入された変数などの内部長をビット単位で返す関数です。
派生型名と同じ名前のデフォールト・コンストラクタによって派生型変数に値を代入しています。
module m_mod
implicit none
type :: t_test1
character :: cha = 'c'
integer :: ix = 0, iy = 0
end type t_test1
type :: t_test2
sequence
character :: cha = 'c'
integer :: ix = 0, iy = 0
end type t_test2
end module m_mod
program typebound
use m_mod
implicit none
type(t_test1) :: a = t_test1('a', 1, 2)
type(t_test2) :: b = t_test2('b', 3, 4)
print *, 'test1 ', storage_size(a) / 8, 'bytes'
print *, 'test2 ', storage_size(b) / 8, 'bytes'
write(8) a
write(9) b
end program typebound
実行結果:intel fortran ver.19
sequence のある方が 3 バイト短くなっています。4 バイト区切りで変数を並べる所を、先頭に 1 バイトの文字があるので sequence を指定しない場合 3 バイト分のダミーで埋め合わせられるために差が生じます。
test1 12 bytes
test2 9 bytes
実行結果:gfortran ver.7,ver.8
test1 12 bytes
test2 12 bytes
gfortran では、意図通りになっていませんw バイナリファイルの中身を見ても勝手に padding されています。コンパイラのオプションを調べても、common 文での alignment をしないオプションしか見つかりませんでした。common か equivalence を使うしかないようです。
sequence のある時の type-bound procedure
type 宣言とサブルーチンの宣言で循環参照がありますが、コンパイラが自動的に解決してくれます。(昔の intel fortran は規格外の type 内 import 宣言が必要でした。)
デフォールト・コンストラクタは、派生型の宣言で初期値を与えてある場合、省略したり要素成分名特定の要素だけに代入できます。つまり派生型のすべての要素成分に初期値を与えることで、やや変則的ですが、いわゆる set_xxx 式のメソッドを自分で書く必要が無くなるようです。
ところが、intel fortran はコンパイラにバグがあるようで、宣言のところでちゃんとサブルーチンをポイントしているのに、関数ポインタに初期値が無いと文句を言ってきます。この場合、初期値の無い要素成分には必ず引数を与えないといけません。gfortran ではこのような問題は起きませんでした。
実例2
サブルーチンの自己引数(以下の場合では this)は、class ではなく type で宣言します。
module m_mod
implicit none
type :: t_test
sequence
character :: cha = 'c'
integer :: ix = 0, iy = 0
procedure (p_test), pointer :: pr => p_test
end type t_test
contains
subroutine p_test(this)
type(t_test), intent(in) :: this
print *, '[', this%cha, ']', this%ix, this%iy
end subroutine p_test
end module m_mod
program typebound
use m_mod
implicit none
type(t_test ) :: x
call x%pr()
x = t_test('x', 99, 999)
! x = t_test('x', 99, 999, p_test) ! intel compiler bug ?
call x%pr()
end program typebound
実行結果
intel fortran/gfortran 共通
[c] 0 0
[x] 99 999
まとめ
コンパイラの問題で、すっきりした結論になりませんでしたが、オブジェクト指向式に派生型に contains で type―bound procedure を与える方式とは別に、関数ポインタにサブルーチン・関数を結びつけることで、type-bound procedure を実現する方法を見てみました。
関数ポインタを利用する方式は、sequence 型や parameterized derived type のようなオブジェクト指向式の class 宣言が出来ない場合にも適用できます。また関数ポインタは、実行時に別のサブルーチン・関数に参照先を付け替えられるので、オブジェクト指向式の contains で static に与える方式よりも柔軟です。