oopfortranpolymorphismoverloading

Procedure Overloading with an allocatable variable as parameter in fortran


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? ?

Edit 1

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.


Solution

  • 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.