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.
CodePudding user response:
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:
- 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
- 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))