Sorush
Sorush

Reputation: 4129

Fortran : generic procedure of parent is called instead of child when allocating child from class of parent

I explain my question in the form of an example.

I have a type (location2d_t) which includes two members x, and y and a type-bound procedure (calcdist2d). The procedure, in addition of (this) with class of (location2d_t), accepts its own type (as second dummy argument) to calculate a distance. Now, I go further and extend the type to (location3d_t) which has z as well.

To re-define the procedure, I cannot override the previous one so I create a new procedure (calcdist3d) with the second argument of type of (location3d_t) and make a generic procedure (calcdist) for them. In other words, second arguments have different types so generic idea is applicable.

In a more general scope, let's say main program here, for the sake of generality I declare my object as class of parent. When I allocate the object with type of child (location3d_t), a call to (calcdist) whose second dummy argument is (location3d_t) refers to parent generic and says

Error: Found no matching specific binding for the call to the GENERIC 'calcdist'

The code is

module point_mod
implicit none
type location2d_t
    integer :: x,y
contains
    procedure :: calcdist2d => calcdistance2d
    procedure :: here => here_location2d
    generic   :: calcdist => calcdist2d 
end type

type, extends(location2d_t) :: location3d_t
    integer :: z
contains
    procedure :: calcdist3d => calcdistance3d
    procedure, public :: here => here_location3d
    generic, public   :: calcdist =>   calcdist3d 
end type        

contains

function calcdistance2d(this,location) result(output)
    class(location2d_t) :: this
    type(location2d_t)  :: location
    integer :: output
    output = int(sqrt(real((location%x-this%x)**2+(location%y-this%y)**2)))
end function

function calcdistance3d(this,location) result(output)
    class(location3d_t) :: this
    type(location3d_t) :: location
    integer :: output
    output = int(sqrt(real((location%x-this%x)**2+ &
    (location%y-this%y)**2+(location%z-this%z)**2)))
end function

subroutine here_location2d(this)
    class (location2d_t) :: this
    print*, "we are in locationd2d_t"
end subroutine

subroutine here_location3d(this)
    class (location3d_t) :: this
    print*, "we are in locationd3d_t"
end subroutine
end module

The module is compiled without any error. The below program is implemented to use the module:

program main
use point_mod
implicit none

class (location2d_t), allocatable :: loc
type (location3d_t) :: dum

allocate(location2d_t::loc)
call loc%here() ! calls  location2d_t procedure

deallocate(loc)

allocate(location3d_t::loc)
call loc%here() !correctly calls procedure of location3d_t 

print*,loc%calcdist(dum) ! gives error

select type (loc)
type is (location3d_t)
   print*,loc%calcdist(dum) ! runs well
end select    

end program

The procedure "Here" finds its dynamic type correctly. Why isn't generic procedure of child (calcdist) explicitly called ? Do I have to use "select type" block always even in this obvious case? N.B.: I checked the code with GNU fortran 4.8 and 4.9, and ifort 14.

Upvotes: 1

Views: 1307

Answers (2)

casey
casey

Reputation: 6915

You can accomplish this behavior without generics with only a slight tweak to your calcdistanceXd functions. The reason you couldn't override the function in your extended type is that the argument type of location was mismatched. If you instead declare location in calcdistance2d to be class(location2d_t) then you can match this in calcdistance3d. You will have to add in a select type construct into calcdistance3d in order to access the members of location3d_t from the polymorphic variable location.

Example:

module point_mod
  implicit none

  type :: location2d_t
    integer :: x, y
  contains
    procedure, public, pass(this) :: calcdist => calcdistance2d
    procedure, public, pass(this) :: here => here_location2d
  end type

  type, extends(location2d_t) :: location3d_t
    integer :: z
  contains
    procedure, public, pass(this) :: calcdist => calcdistance3d
    procedure, public, pass(this) :: here => here_location3d
  end type
contains

  function calcdistance2d(this, location) result(output)
    class(location2d_t) :: this
    class(location2d_t)  :: location
    integer :: output
        output = int(sqrt(real((location%x-this%x)**2+(location%y-this%y)**2)))
  end function

  function calcdistance3d(this,location) result(output)
    class(location3d_t) :: this
    class(location2d_t) :: location
    integer :: output
    select type (location)
      type is (location3d_t)
        output = int(sqrt(real((location%x-this%x)**2+ &
        (location%y-this%y)**2+(location%z-this%z)**2)))
      class default 
        output = -1
    end select
  end function

  subroutine here_location2d(this)
    class (location2d_t) :: this
    print*, "we are in locationd2d_t"
  end subroutine

  subroutine here_location3d(this)
    class (location3d_t) :: this
    print*, "we are in locationd3d_t"
  end subroutine
end module

With this version of point_mod, your example program works:

program main
  use point_mod
  implicit none
  class (location2d_t), allocatable :: loc
  type (location3d_t) :: dum

  allocate(location2d_t::loc)
  call loc%here() ! calls  location2d_t procedure

  deallocate(loc)

  allocate(location3d_t::loc)
  call loc%here() !correctly calls procedure of location3d_t 

  print*,loc%calcdist(dum) 

end program

It is true this approach still requires a select type, but it is hidden in the module implementation rather than being required by users of the module.

Upvotes: 0

credondo
credondo

Reputation: 744

Yes, you have to use "select type". Outside the "type is" block, loc is polymorphic. Only inside type is (location3d_t), loc has a type and can be passed as dummy argument with defined type.

Generic procedures are always not overridden when the type is extended, so in location3d_t, calcdist is the generic binding for calcdist3d and calcdist2d and loc needs a specific type when calling calcdist to find the appropriate procedure.

When location2d_t is extended, to location3d_t, here binding is overriden and there is only one procedure associated to loc%here() so can be called outside the "type is" block

Upvotes: 1

Related Questions