classfortranoverridingfortran2008

How to override user-defined I/O procedures?


I have an abstract class with my read/write methods for unformatted binary streams. I also have some classes inherited from the abstract one and some of them have additional components I'd also like to serialize.

So, I want the first set of methods to serve as a "default" behavior and the methods overriding it inside the inherited classes to be used just by those specific classes.

I've tried to implement it like this:

module m
    implicit none

    type, abstract :: a
        integer, public :: num
    contains
        procedure :: write_a
        procedure :: read_a

        generic            :: write(unformatted) => write_a
        generic            :: read(unformatted)  => read_a
    end type a

    type, extends(a) :: b
        integer, public :: num2
    contains
        procedure :: write_b
        procedure :: read_b

        generic :: write(unformatted) => write_b
        generic :: read(unformatted) => read_b
   end type b

    type, extends(a) :: c
    end type c

contains

    subroutine write_a(this, unit, iostat, iomsg)
        class(a), intent(in)    :: this
        integer, intent(in)         :: unit
        integer, intent(out)        :: iostat
        character(*), intent(inout) :: iomsg

        write(unit, iostat=iostat, iomsg=iomsg) this%num
    end subroutine write_a

    subroutine read_a(this, unit, iostat, iomsg)
        class(a), intent(inout) :: this
        integer, intent(in)         :: unit
        integer, intent(out)        :: iostat
        character(*), intent(inout) :: iomsg

        read(unit, iostat=iostat, iomsg=iomsg) this%num
    end subroutine read_a

    subroutine write_b(this, unit, iostat, iomsg)
        class(b), intent(in)    :: this
        integer, intent(in)         :: unit
        integer, intent(out)        :: iostat
        character(*), intent(inout) :: iomsg

        write(unit, iostat=iostat, iomsg=iomsg) this%num, this%num2
    end subroutine write_b

    subroutine read_b(this, unit, iostat, iomsg)
        class(b), intent(inout) :: this
        integer, intent(in)         :: unit
        integer, intent(out)        :: iostat
        character(*), intent(inout) :: iomsg

        read(unit, iostat=iostat, iomsg=iomsg) this%num, this%num2
    end subroutine read_b
end module m

program mwe
    use m

    implicit none

    class(a), allocatable :: o1, o2, o3

    o1 = b(1,2)
    o2 = c(3)

    open(123, file='test5.dat', form='unformatted')
        write(123) o1
    close(123)

    allocate(b :: o3)

    open(123, file='test5.dat', form='unformatted')
        read(123) o3
    close(123)

    write(*,*) o3%num, o3%num2
end program mwe

But I'm getting following error:

test5.f90(29): error #8638: The type/rank signature for arguments of this specific subroutine is identical to another specific subroutine that shares the same defined I/O.   [WRITE_A]
    subroutine write_a(this, unit, iostat, iomsg)
---------------^
test5.f90(86): error #6460: This is not a field name that is defined in the encompassing structure.   [NUM2]
    write(*,*) o3%num, o3%num2
--------------------------^

To me it seems, like the write method in the class a can't be overriden. How can I implement it properly?


Solution

  • This isn't a problem entirely to do with defined input/output procedures, but generic bindings and type-bound procedures more widely.

    Your type b has the type-bound procedures as though (through the extension) it were defined like

    type b
      integer, public :: num, num2
     contains
        procedure :: write_a, write_b
        procedure :: read_a, read_b
        generic   :: write(unformatted) => write_a, write_b
        generic   :: read(unformatted)  => read_a, read_n
    end type
    

    The bindings write_a and write_b are indeed ambiguous (as are read_a and read_b). [More detail on that elsewhere.]

    You don't really need those write_a and read_a bindings, so they should instead be overridden:

    type, abstract :: a
        integer, public :: num
     contains
        procedure :: write => write_a
        procedure :: read => read_a
    
        generic   :: write(unformatted) => write
        generic   :: read(unformatted)  => read
    end type a
    
    type, extends(a) :: b
        integer, public :: num2
     contains
        procedure :: write => write_b
        procedure :: read => read_b
    end type b
    

    Here the write and read bindings of type b override those of type a. The generic binding of write(unformatted) and read(unformatted) retain the mapping to the (now overridden) bindings in b.