I am learning oop in Fortran. And I wonder about the interest of overloading type bound procedure when the type of an argument is not known at compile time.
Let me explain, step by step my problem (These programs are only examples to increase my skill in fortran)
First program : overload of a type bound procedure
module my_mod
implicit none
type :: my_type_t
character(len=128) :: my_text
contains
procedure :: integer_print, real_print
generic :: my_print => integer_print, real_print
end type my_type_t
contains
subroutine integer_print(this, my_int)
class(my_type_t), intent(in) :: this
integer, intent(in) :: my_int
write (*,"(a,a,i0)") trim(this%my_text),' integer ', my_int
end subroutine integer_print
subroutine real_print(this, my_real)
class(my_type_t), intent(in) :: this
real, intent(in) :: my_real
write (*,"(a,a,f0.3)") trim(this%my_text),' real ', my_real
end subroutine real_print
end module my_mod
program my_pgm
use my_mod
implicit none
type(my_type_t) :: my_var
my_var%my_text = "Hello"
call my_var%my_print(10)
call my_var%my_print(9.9)
end program my_pgm
It gave the expected result. Either integer_print
or real_print
is used, depending of the type of my_print
argument (integer or real).
Second program (or step, I should write) : an abstract type is used and there are two derived types : my_int_t
and my_real_t
. It is very similar to the first example. But, two types are used, one for integer and the other for real.
module my_mod
implicit none
type, abstract :: my_abstract_t
end type my_abstract_t
type, extends(my_abstract_t) :: my_int_t
integer :: an_integer
end type my_int_t
type, extends(my_abstract_t) :: my_real_t
real :: a_real
end type my_real_t
type :: my_type_t
character(len=128) :: my_text
contains
procedure :: integer_print, real_print
generic :: my_print => integer_print, real_print
end type my_type_t
contains
subroutine integer_print(this, my_int)
class(my_type_t), intent(in) :: this
type(my_int_t), intent(in) :: my_int
write (*,"(a,a,i0)") trim(this%my_text),' integer ', my_int%an_integer
end subroutine integer_print
subroutine real_print(this, my_real)
class(my_type_t), intent(in) :: this
type(my_real_t), intent(in) :: my_real
write (*,"(a,a,f0.3)") trim(this%my_text),' real ', my_real%a_real
end subroutine real_print
end module my_mod
program my_pgm
use my_mod
implicit none
type(my_type_t) :: my_var
type(my_int_t) :: my_int
type(my_real_t) :: my_real
my_var%my_text = "Hello"
my_int%an_integer = 10
my_real%a_real = 9.9
call my_var%my_print(my_int)
call my_var%my_print(my_real)
end program my_pgm
The type of my_int
and my_real
is known at compile type, so the output is correct. And the same call my_var%my_print(...)
is used whatever the variable type (my_int_t
or my_real_t
), as in the first example.
But, now this is my problem.
program my_pgm
use my_mod
implicit none
type(my_type_t) :: my_var
class(my_abstract_t), allocatable :: my_number
allocate(my_int_t::my_number)
! or allocate(my_real_t::my_number)
my_var%my_text = "Hello"
select type (my_number)
type is (my_int_t)
my_number%an_integer = 10
type is (my_real_t)
my_number%a_real = 9.9
end select
select type (my_number)
type is (my_int_t)
call my_var%my_print(my_number)
type is (my_real_t)
call my_var%my_print(my_number)
end select
end program my_pgm
The type of my_number
is not known at compile time. So, I must use a part of code that I found really redundant :
select type (my_number)
type is (my_int_t)
call my_var%my_print(my_number)
type is (my_real_t)
call my_var%my_print(my_number)
end select
I would have preferred to write only one line : call my_var%my_print(...)
as in the first and second examples. Should I conclude that overloading of procedure have not interest in the third example and it is better to use integer_print
and real_print
directly in the select type
block ? Or Is there something I haven't understood? ?
Following the comments of Francescalus, if I have well understood, I could not avoid the select type
block. So, I modify the program in the following way.
module my_mod
implicit none
type, abstract :: my_abstract_t
end type my_abstract_t
type, extends(my_abstract_t) :: my_int_t
integer :: an_integer
end type my_int_t
type, extends(my_abstract_t) :: my_real_t
real :: a_real
end type my_real_t
type :: my_type_t
character(len=128) :: my_text
contains
procedure, private :: real_print, integer_print
procedure, public :: my_print
end type my_type_t
contains
subroutine integer_print(this, my_int)
class(my_type_t), intent(in) :: this
type(my_int_t), intent(in) :: my_int
write (*,"(a,a,i0)") trim(this%my_text),' integer ', my_int%an_integer
end subroutine integer_print
subroutine real_print(this, my_real)
class(my_type_t), intent(in) :: this
type(my_real_t), intent(in) :: my_real
write (*,"(a,a,f0.3)") trim(this%my_text),' real ', my_real%a_real
end subroutine real_print
subroutine my_print(this,my_number)
class(my_type_t), intent(in) :: this
class(my_abstract_t), intent(in) :: my_number
select type (my_number)
type is (my_int_t)
call this%integer_print(my_number)
type is (my_real_t)
call this%real_print(my_number)
end select
end subroutine my_print
end module my_mod
program my_pgm
use my_mod
implicit none
type(my_type_t) :: my_var
class(my_abstract_t), allocatable :: my_number1, my_number2
my_number1 = my_int_t(an_integer = 10)
my_number2 = my_real_t(a_real = 9.9)
my_var%my_text = "Hello"
call my_var%my_print(my_number1)
call my_var%my_print(my_number2)
end program my_pgm
Any comments would be appreciated.
One can use double dispatch, also called the visitor pattern.
This is a sample implementation for your code. It has the obvious limitation that the my_type_t
type must already be known. However, the use of select_type
is avoided and further types extending my_abstract_type
can be added freely.
module my_mod
implicit none
type :: my_type_t
character(len=128) :: my_text
contains
procedure, public :: my_print
end type my_type_t
type, abstract :: my_abstract_t
contains
procedure(get_printed_from_my_type_t_interface), deferred :: get_printed_from_my_type_t
end type my_abstract_t
abstract interface
subroutine get_printed_from_my_type_t_interface(this, printer)
import
class(my_abstract_t), intent(in) :: this
type(my_type_t), intent(in) :: printer
end subroutine
end interface
type, extends(my_abstract_t) :: my_int_t
integer :: an_integer
contains
procedure :: get_printed_from_my_type_t => integer_print
end type my_int_t
type, extends(my_abstract_t) :: my_real_t
real :: a_real
contains
procedure :: get_printed_from_my_type_t => real_print
end type my_real_t
contains
subroutine integer_print(this, printer)
class(my_int_t), intent(in) :: this
type(my_type_t), intent(in) :: printer
write (*,"(a,a,i0)") trim(printer%my_text),' integer ', this%an_integer
end subroutine integer_print
subroutine real_print(this, printer)
class(my_real_t), intent(in) :: this
type(my_type_t), intent(in) :: printer
write (*,"(a,a,f0.3)") trim(printer%my_text),' real ', this%a_real
end subroutine real_print
subroutine my_print(this,my_number)
class(my_type_t), intent(in) :: this
class(my_abstract_t), intent(in) :: my_number
call my_number%get_printed_from_my_type_t(this)
end subroutine my_print
end module my_mod
program my_pgm
use my_mod
implicit none
type(my_type_t) :: my_var
class(my_abstract_t), allocatable :: my_number1, my_number2
my_number1 = my_int_t(an_integer = 10)
my_number2 = my_real_t(a_real = 9.9)
my_var%my_text = "Hello"
call my_var%my_print(my_number1)
call my_var%my_print(my_number2)
end program my_pgm
The main point is doing another dispatch when having type(my_type_t)
already available and shifting the implementations of the printing to the individual types with the numeric data deriving from my_abstract_t
which have to know the structure of my_type_t
- that is a drawback.