Home > Enterprise >  oop fortran : why my program does not choose the good subroutine?
oop fortran : why my program does not choose the good subroutine?

Time:06-09

I am learning oop in fortran.

This is my code and the output surprise me :

module my_module

  implicit none

  type basic_t
    real :: basic
  end type basic_t

  type, extends(basic_t) :: extended_t
    real :: extended
  end type extended_t

  interface print_type
     module procedure print_basic_type
     module procedure print_extended_type
  end interface print_type

  contains

  subroutine print_basic_type(basic) 
    type(basic_t), intent(in) :: basic

    print *, 'in sub : print_basic_type'
  end subroutine print_basic_type

  subroutine print_extended_type(extended) 
    type(extended_t), intent(in) :: extended

    print *, 'in sub : print_extended_type'
  end subroutine print_extended_type
  
  subroutine print_using_class(basic_or_extended)
    class(basic_t), intent(in) :: basic_or_extended
    
    select type (basic_or_extended)
        type is (basic_t)
            print *, 'the type is basic'
        type is (extended_t)
            print *, 'the type is extended'
    end select
    call print_type(basic_or_extended)
  end subroutine print_using_class
    
end module my_module


program main

  use my_module
  
  type(basic_t) :: basic
  type(extended_t) :: extended
  
  call print_type(basic)
  call print_type(extended)
  print *,'------'
  call print_using_class(basic)
  print *,'------'
  call print_using_class(extended)
 
end program main

This is the output :

 in sub : print_basic_type
 in sub : print_extended_type
 ------
 the type is basic
 in sub : print_basic_type
 ------
 the type is extended
 in sub : print_basic_type

When I use the interface print_type directly, it works. But when I call the subroutine print_using_class, it does not work for the extended type although the type is well recognized (cf select type before print_type call in subroutine print_using_class). Is it the output expected ? And why ?

Thanks for answer.

Edit 1

As suggested in the comments, I should use "bindings". After many attempts, this is my new code.

module my_module

  implicit none
  
  type, abstract :: abstract_t
  contains
    procedure(print_abstract_t), deferred :: print_sub
  end type abstract_t
  
  abstract interface
    subroutine print_abstract_t(this)
      import abstract_t
      class(abstract_t), intent(in) :: this
    end subroutine print_abstract_t
  end interface


  type, extends(abstract_t) :: basic_t
    real :: basic
  contains
    procedure :: print_sub => print_basic_type
  end type basic_t

  type, extends(basic_t) :: extended_t
    real :: extended
  contains
    procedure :: print_sub => print_extended_type
  end type extended_t

  contains

  subroutine print_basic_type(this) 
    class(basic_t), intent(in) :: this

    print *, 'in sub : print_basic_type'
  end subroutine print_basic_type

  subroutine print_extended_type(this) 
    class(extended_t), intent(in) :: this

    print *, 'in sub : print_extended_type'
  end subroutine print_extended_type
  
  subroutine print_using_class(basic_or_extended)
    class(basic_t), intent(in) :: basic_or_extended
    
    select type (basic_or_extended)
        type is (basic_t)
            print *, 'the type is basic'
        type is (extended_t)
            print *, 'the type is extended'
    end select
    call basic_or_extended%print_sub()
  end subroutine print_using_class
    
end module my_module


program main

  use my_module
  
  type(basic_t) :: basic
  type(extended_t) :: extended
  
  call basic%print_sub()
  call extended%print_sub()

  print *,'------'
  call print_using_class(basic)
  print *,'------'
  call print_using_class(extended)
 
end program main

I am surprised but it works. As I am learning OOP in fortran, any criticism would be appreciated.

CodePudding user response:

In the subroutine print_using_class there is a reference to the generic print_type with actual argument basic_or_extended.

The generic print_type has two specific interfaces, print_basic_type and print_extended_type. (Because these specific procedures use non-polymorphic arguments they are not ambiguous (Fortran 2018, 15.4.3.4.5).) In the reference to the generic, we need to resolve the reference to a specific procedure. (F2018, 15.5.5.2)

Resolution to which of these two specific procedures is referenced is not based on dynamic type; resolution is based on declared type.

Why?

The relevant dummy arguments are:

  • type(basic_t) in print_basic_type
  • type(extended_t) in print_extended_type

The referenced specific procedure is the one where the dummy argument is type compatible with class(basic_or_extended) (F2018, 15.5.2.4). (Recall, only one specific can be consistently referenced.)

type(basic_t) is type compatible with class(basic_t); type(extended_t) is not (F2018, 7.3.2.3 p.5). The reference to the generic print_type is always to print_basic_type. (Note that a class(basic_t) is type compatible with a type(extended_t) but we need the dummy to be type compatible with the actual, not the actual to be type compatible with the dummy: type compatability is not symmetric. )

If you want to use a generic and resolve to the dynamic type of the actual argument, well you can't. What you can do is have another object of the desired declared type:


  subroutine print_using_class(basic_or_extended)
    class(basic_t), intent(in) :: basic_or_extended
    
    select type (basic_or_extended)
        type is (basic_t)
            print *, 'the type is basic'
            call print_type(basic_or_extended)
        type is (extended_t)
            print *, 'the type is extended'
            call print_type(basic_or_extended)
    end select
  end subroutine print_using_class

But ideally, you wouldn't be doing this. Instead, you'll be using bindings and type-bound procedures where this dynamic resolution just falls out.

  • Related