Constructor of derived types
Asked Answered
S

2

5

I am trying to write a constructor for a derived type of an abstract one to solve this other question, but it seems that it's not working, or better, it isn't called at all.

The aim is to have a runtime polymorphism setting the correct number of legs of an animal.

These are the two modules:

animal

module animal_module
    implicit none

    type, abstract :: animal
        private
        integer, public :: nlegs = -1
    contains
        procedure :: legs
    end type animal

contains

    function legs(this) result(n)
        class(animal), intent(in) :: this
        integer :: n

        n = this%nlegs
    end function legs

cat

module cat_module
    use animal_module, only : animal
    implicit none

    type, extends(animal) :: cat
        private
    contains
        procedure :: setlegs => setlegs
    end type cat

    interface cat
        module procedure init_cat
    end interface cat

contains

    type(cat) function init_cat(this)
        class(cat), intent(inout) :: this
        print *, "Cat!"
        this%nlegs = -4
    end function init_cat

main program

program oo
    use animal_module
    use cat_module
    implicit none

    type(cat) :: c
    type(bee) :: b

    character(len = 3) :: what = "cat"

    class(animal), allocatable :: q

    select case(what)
    case("cat")
        print *, "you will see a cat"
        allocate(cat :: q)
        q = cat() ! <----- this line does not change anything

    case default
        print *, "ohnoes, nothing is prepared!"
        stop 1
    end select

    print *, "this animal has ", q%legs(), " legs."
    print *, "cat  animal has ", c%legs(), " legs."
end program

The constructor isn't called at all, and the number of legs still remains to -1.

Sycosis answered 2/11, 2019 at 6:11 Comment(0)
C
4

The available non-default constructor for the cat type is given by the module procedure init_cat. This function you have defined like

type(cat) function init_cat(this)
    class(cat), intent(inout) :: this
end function init_cat

It is a function with one argument, of class(cat). In your later reference

q = cat()

There is no specific function under the generic cat which matches that reference: the function init_cat does not accept a no-argument reference. The default structure constructor is instead used.

You must reference the generic cat in a way matching your init_cat interface to have that specific function called.

You want to change your init_cat function to look like

type(cat) function init_cat()
    ! print*, "Making a cat"
    init_cat%nlegs = -4
end function init_cat

Then you can reference q=cat() as desired.

Note that in the original, you are attempting to "construct" a cat instance, but you aren't returning this constructed entity as the function result. Instead, you are modifying an argument (already constructed). Structure constructors are intended to be used returning such useful things.

Note also that you don't need to

allocate (cat :: q)
q = cat()

The intrinsic assignment to q already handles q's allocation.

Cchaddie answered 2/11, 2019 at 13:11 Comment(0)
D
2

FWIW, here is some sample code comparing three approaches (method = 1: sourced allocation, 2: polymorphic assignment, 3: mixed approach).

module animal_module
    implicit none

    type, abstract :: animal_t
        integer :: nlegs = -1
    contains
        procedure :: legs   !! defines a binding to some procedure
    endtype

contains
    function legs(this) result(n)
        class(animal_t), intent(in) :: this
            !! The passed variable needs to be declared as "class"
            !! to use this routine as a type-bound procedure (TBP).
        integer :: n
        n = this % nlegs
    end
end

module cat_module
    use animal_module, only : animal_t
    implicit none

    type, extends(animal_t) :: cat_t
    endtype

    interface cat_t   !! overloads the definition of cat_t() (as a procedure)
        module procedure make_cat
    end interface

contains
    function make_cat() result( ret )   !! a usual function
        type(cat_t) :: ret   !<-- returns a concrete-type object
        ret % nlegs = -4
    end
end

program main
    use cat_module, only: cat_t, animal_t
    implicit none
    integer :: method

    type(cat_t) :: c
    class(animal_t), allocatable :: q

    print *, "How to create a cat? [method = 1,2,3]"
    read *, method

    select case ( method )
        case ( 1 )
            print *, "1: sourced allocation"

            allocate( q, source = cat_t() )

            !! An object created by a function "cat_t()" is used to
            !! allocate "q" with the type and value taken from source=.
            !! (Empirically most stable for different compilers/versions.)

        case ( 2 )
            print *, "2: polymorphic assignment"

            q = cat_t()

            !! Similar to sourced allocation. "q" is automatically allocated.
            !! (Note: Old compilers may have bugs, so tests are recommended...)

        case ( 3 )
            print *, "3: mixed approach"

            allocate( cat_t :: q )
            q = cat_t()

            !! First allocate "q" with a concrete type "cat_t"
            !! and then assign a value obtained from cat_t().

        case default ; stop "unknown method"
    endselect

    c = cat_t()
    !! "c" is just a concrete-type variable (not "allocatable")
    !! and assigned with a value obtained from cat_t().

    print *, "c % legs() = ", c % legs()
    print *, "q % legs() = ", q % legs()
end

--------------------------------------------------
Test

$ gfortran test.f90   # using version 8 or 9

$ echo 1 | ./a.out
 How to create a cat? [method = 1,2,3]
 1: sourced allocation
 c % legs() =           -4
 q % legs() =           -4

$ echo 2 | ./a.out
 How to create a cat? [method = 1,2,3]
 2: polymorphic assignment
 c % legs() =           -4
 q % legs() =           -4

$ echo 3 | ./a.out
 How to create a cat? [method = 1,2,3]
 3: mixed approach
 c % legs() =           -4
 q % legs() =           -4

--------------------------------------------------
Side notes

* It is also OK to directly use make_cat() to generate a value of cat_t:
  e.g., allocate( q, source = make_cat() ) or q = make_cat().
  In this case, we do not need to overload cat_t() via interface.

* Another approach is to write an "initializer" as a type-bound procedure,
  and call it explicitly as q % init() (after allocating it via
  allocate( cat_t :: q )). If the type contains pointer components,
  this approach may be more straightforward by avoiding copy of
  components (which can be problematic for pointer components).
Dastardly answered 4/11, 2019 at 5:17 Comment(2)
With this comparison, it's perhaps worth noting that there are subtle differences for finalizable types.Cchaddie
@Cchaddie I hope I could add some notes about "finalizable types", but I've never used "final" up to now so cannot explain...(instead, I usually call a "fin()" routine manually). I will play with it to get experience.Dastardly

© 2022 - 2024 — McMap. All rights reserved.