Search code examples
oopmodulefortran

Fortran : Is it possible to overload a procedure in a abstract interface?


I wrote the following code where an abstract type (shape_t) could be extended to a segment, a rectangle or a box. There are two procedures, one to initialize the variable and the other to print the size. This code gave the expected result.
A segment, a rectangle or a box could be initialized and the size could be printed.

module myModule
    implicit none
    
    type, abstract :: shape_t
    contains
        procedure(abstract_init), deferred :: init
        procedure(abstract_print), deferred :: print_size
    end type shape_t
    
    abstract interface 
        subroutine abstract_init(this,length1,length2,length3)
            import shape_t
            class(shape_t), intent(inout) :: this
            real, intent(in) :: length1,length2,length3
        end subroutine abstract_init

        subroutine abstract_print(this)
            import shape_t
            class(shape_t), intent(in) :: this
        end subroutine abstract_print     
    end interface
    
    type, extends(shape_t) :: line_t
        real :: length
    contains
        procedure :: init => init_line
        procedure :: print_size => print_linesize
    end type line_t
   
     type, extends(shape_t) :: rectangle_t
        real :: length,width
    contains
        procedure :: init => init_rectangle
        procedure :: print_size => print_rectanglesize
    end type rectangle_t
    
    type, extends(shape_t) :: box_t
        real :: length,width,height
    contains
        procedure :: init => init_box
        procedure :: print_size => print_boxsize
    end type box_t
    
contains
    subroutine init_line(this,length1,length2,length3)
        class(line_t), intent(inout) :: this
        real, intent(in) :: length1,length2,length3
        this%length = length1
    end subroutine init_line
       
    subroutine print_linesize(this)
        class(line_t), intent(in) :: this
        print*,'Line size',this%length,'meter'
    end subroutine print_linesize
    
    subroutine init_rectangle(this,length1,length2,length3)
        class(rectangle_t), intent(inout) :: this
        real, intent(in) :: length1,length2,length3
        this%length = length1
        this%width = length2
    end subroutine init_rectangle
       
    subroutine print_rectanglesize(this)
        class(rectangle_t), intent(in) :: this
        print*,'Rectangle area',this%length*this%width,'meter^2'
    end subroutine print_rectanglesize
    
        subroutine init_box(this,length1,length2,length3)
        class(box_t), intent(inout) :: this
        real, intent(in) :: length1,length2,length3
        this%length = length1
        this%width = length2
        this%height = length3
    end subroutine init_box
       
    subroutine print_boxsize(this)
        class(box_t), intent(in) :: this
        print*,'Box volume',this%length*this%width*this%height,'meter^3'
    end subroutine print_boxsize
        
end module myModule

program main

    use myModule
    
    implicit none
    
    class(line_t), allocatable :: segment
    class(rectangle_t), allocatable :: rectangle
    class(box_t), allocatable :: box
    
    allocate(line_t::segment)
    call segment%init(12.0,0.0,0.0)
    call segment%print_size()
    
    allocate(rectangle_t::rectangle)
    call rectangle%init(12.0,5.0,0.0)
    call rectangle%print_size()
    
    allocate(box_t::box)
    call box%init(12.0,5.0,2.0)
    call box%print_size()
    
end program main

But as there are no the same number of dimensions between segment, rectangle and box and I do not know how to overload a procedure in a abstract interface, I use 3 real parameters to initialise variables (even for segment and rectangle where only one or two are needed, respectively). I wonder how is it possible to modify this code, to change in the main program :
call segment%init(12.0,0.0,0.0) and call rectangle%init(12.0,5.0,0.0) to call segment%init(12.0) and call rectangle%init(12.0,5.0)
Thanks for answer.


Solution

  • The deferred procedure in the abstract interface needs to have the same interface in all extended classes. This is unfortunate, but Fortran has no easy way to work around this. I think you have two options:

    1. In a relatively simple case like this, you could set some of the arguments to optional, and then check inside each class implementation that all inputs you need have already been provided, like:
            subroutine abstract_init(this,length1,length2,length3)
                import shape_t
                class(shape_t), intent(inout) :: this
                real, intent(in) :: length1
                real, intent(in), optional :: length2,length3
            end subroutine abstract_init
    
    1. Otherwise, you can have a generic interface to init. In other words, you're going to write more wrappers to the initialization function, that allow for different inputs. This is an example:
    module myModule
        implicit none
    
        type, abstract :: shape_t
        contains
    
            ! Put init in a generic interface
            procedure(abstract_init), deferred, private  :: shared_init
            generic :: init => shared_init
    
            procedure(abstract_print), deferred :: print_size
        end type shape_t
    
        abstract interface
            subroutine abstract_init(this,length1,length2,length3)
                import shape_t
                class(shape_t), intent(inout) :: this
                real, intent(in) :: length1,length2,length3
            end subroutine abstract_init
    
            subroutine abstract_print(this)
                import shape_t
                class(shape_t), intent(in) :: this
            end subroutine abstract_print
        end interface
    
        type, extends(shape_t) :: line_t
            real :: length
        contains
            procedure :: shared_init => init_line
            procedure :: init_line_1d
            generic   :: init => init_line_1d
            procedure :: print_size => print_linesize
        end type line_t
    
         type, extends(shape_t) :: rectangle_t
            real :: length,width
        contains
            procedure :: shared_init => init_rectangle
            procedure :: init_rectangle_2d
            generic   :: init => init_rectangle_2d
            procedure :: print_size => print_rectanglesize
        end type rectangle_t
    
        type, extends(shape_t) :: box_t
            real :: length,width,height
        contains
            procedure :: shared_init => init_box
            procedure :: print_size => print_boxsize
        end type box_t
    
    contains
        subroutine init_line_1d(this,length1)
            class(line_t), intent(inout) :: this
            real, intent(in) :: length1
            this%length = length1
        end subroutine init_line_1d
        subroutine init_line(this,length1,length2,length3)
            class(line_t), intent(inout) :: this
            real, intent(in) :: length1,length2,length3
            call init_line_1d(this,length1)
        end subroutine init_line
    
        subroutine print_linesize(this)
            class(line_t), intent(in) :: this
            print*,'Line size',this%length,'meter'
        end subroutine print_linesize
    
        subroutine init_rectangle(this,length1,length2,length3)
            class(rectangle_t), intent(inout) :: this
            real, intent(in) :: length1,length2,length3
            call init_rectangle_2d(this,length1,length2)
        end subroutine init_rectangle
        subroutine init_rectangle_2d(this,length1,length2)
            class(rectangle_t), intent(inout) :: this
            real, intent(in) :: length1,length2
            this%length = length1
            this%width = length2
        end subroutine init_rectangle_2d
    
        subroutine print_rectanglesize(this)
            class(rectangle_t), intent(in) :: this
            print*,'Rectangle area',this%length*this%width,'meter^2'
        end subroutine print_rectanglesize
    
            subroutine init_box(this,length1,length2,length3)
            class(box_t), intent(inout) :: this
            real, intent(in) :: length1,length2,length3
            this%length = length1
            this%width = length2
            this%height = length3
        end subroutine init_box
    
        subroutine print_boxsize(this)
            class(box_t), intent(in) :: this
            print*,'Box volume',this%length*this%width*this%height,'meter^3'
        end subroutine print_boxsize
    
    end module myModule
    
    program main
    
        use myModule
    
        implicit none
    
        class(line_t), allocatable :: segment
        class(rectangle_t), allocatable :: rectangle
        class(box_t), allocatable :: box
    
        allocate(line_t::segment)
        call segment%init(12.0)
        call segment%print_size()
    
        allocate(rectangle_t::rectangle)
        call rectangle%init(12.0,5.0)
        call rectangle%print_size()
    
        allocate(box_t::box)
        call box%init(12.0,5.0,2.0)
        call box%print_size()
    
    end program main
    
    

    In other words, the deferred procedure always represents a shared common procedure to all instances. Also keep in mind that you can always use default initializers, like:

    allocate(segment  ,source=line_t(length=12.0))
    allocate(rectangle,source=rectangle_t(length=12.0,width=5.0))
    allocate(box      ,source=box_t(length=12.0,width=5.0,depth=2.0))