Questions about derived type intrinsic assignment

Why even use select type at all? If you find yourself reaching for select type, it typically means the type hierarchy doesn’t fulfil the Liskov Substitution Principle. In this particular case I think there are better options available:

For the polymorphic “container” type that @rwmsu showed,

type, abstract :: base_t
! some basic data
contains
! a small number of deferred procedures
end type

type :: base_array_t
   class(base_t), allocatable :: vals        ! polymorphic component (!)
 end type

if the goal is to copy the polymorphic %vals component, select type is not needed for example to swap two values, and intrinsic assignment can be used instead:

type(base_array_t), allocatable :: base_array(`:`)

type(child1_t) :: achild1
type(child2_t) :: achild2

allocate(base_array(2))
allocate(base_array(1)%vals, source=achild1)
allocate(base_array(2)%vals, source=achild2)

! Swap the contents of elements 1 and 2
tmp = array(1)%vals
array(1)%vals = array(2)%vals
array(2)%vals = tmp

! The dynamic types have changed
print *, same_type_as(array(1)%vals, achild2), same_type_as(array(2)%vals, achild1)

Imagine there were three elements, two of which had the same type,

allocate(base_array(1)%vals, source=achild1)       ! child1_t
allocate(base_array(2)%vals, source=achild2)       ! child2_t
allocate(base_array(3)%vals, source=child1_t(...)) ! child1_t

! copy value of element 3 into element 1
base_array(1)%vals = base_array(3)%vals

According to MRC (“the red book”), Section 15.5, page 300, this is permitted and on top

if the variable is already allocated with the correct type (and shape and deferred type parameter values, if applicable), no reallocation is done; …

The copy itself occurs (second paragraph of Section 15.5)

just as in normal assignment (with shallow copying for any pointer components and deep copying for any allocatable components)

In addition, in section 6.11, page 114, on allocatable components of derived types, it is stated that

if the component of a variable is already allocated with the same shape, the compiler may choose to avoid the overheads of deallocation and reallocation. (emphasis mine)

So in theory, if the type and dimensions match, this should not be that expensive. You can verify this “runtime” invariant on the dynamic type holds using the intrinsic same_type_as(a,b) function and issue an error stop if it fails.


In the more complicated case, where the types in the hierarchy may contain pointer components, and deep copies are desired, introducing an abstract copy operation, as in the prototype pattern, is one of the possible solutions:

type, abstract :: base_t
! some basic data
contains
! a small number of deferred procedures
   procedure(base_clone), deferred :: clone        ! <- prototype pattern
end type

interface
   function base_clone(this) result(clone)
      import base_t
      class(base_t), intent(in) :: this
      class(base_t), allocatable :: clone
   end function
end interface

You’d use it like this:

class(base_t), allocatable :: a, b

a = child1_t(...)
b = a%clone()       ! a knows how to make a deep copy of itself

A good compiler should be able to elide unnecessary allocations or assignments (copies) of the type components in this case (one caveat is if you also need a finalizer, then things will be slightly different).

In contrast to select type, where the types must be known in advance, the prototype pattern would allow you to dynamically load a plug-in. Here is a rough sketch:

module base_module
implicit none
public

type, abstract :: base_t
! some basic data
contains
! a small number of deferred procedures
   procedure(base_clone), deferred :: clone        ! <- prototype pattern
end type

interface
   function base_clone(this) result(clone)
      import base_t
      class(base_t), intent(in) :: this
      class(base_t), allocatable :: clone
   end function
end interface

end module

module extension

    use, intrinsic :: iso_c_binding
    use base_module, only: base_t
    
    implicit none
    private

    public :: create_from_plugin

    type, extends(base_t) :: child3
        type(c_ptr) :: handle = c_null_ptr
    contains
        ! deferred procedures which call strategies
        ! implemented in the dynamically loaded plugin
        procedure :: clone => plugin_clone
    end type

contains

    function create_from_plugin(name) result(this)
        character(len=*), intent(in) :: name
        class(base_t), allocatable :: this
        interface
            function dlopen(name,flags) bind(c,name="dlopen")
                import c_char, c_ptr, c_int
                character(kind=c_char), intent(in) :: name(*)
                integer(c_int), value :: flags
                type(c_ptr) :: dlopen
            end function
        end interface

        this = child3(dlopen(trim(name)//c_null_char, flags=0_c_int))

    end function

    function plugin_clone(this) result(clone)
        class(child3), intent(in) :: this
        class(base_t), allocatable :: clone
        type(child3), allocatable :: tmp

        interface
            function dlsym(handle,symbol) bind(c,name="dlsym")
                import c_char, c_ptr, c_int, c_funptr
                type(c_ptr), value :: handle
                character(kind=c_char), intent(in) :: symbol(*)
                type(c_funptr) :: dlsym
            end function
        end interface

       ! Call procedure in libhandle
       type(c_funptr) :: func
       procedure(), pointer :: ffunc

       print *, "mock clone method"
       func = dlsym(this%handle,"__clone_procedure")
       ! call c_f_procpointer(func,ffunc)
       ! call ffunc(...)

       ! Use "copy and swap" idiom to populate the object
       allocate(tmp)
       tmp%handle = this%handle ! shallow copy library handle
       call move_alloc(from=tmp,to=clone)

    end function

end module

program main

use base_module
use extension
implicit none
class(base_t), allocatable :: a, b
a = create_from_plugin("my_plugin.so")  ! the dynamic type is private (!)
b = a%clone() 
end program
1 Like