Questions about derived type intrinsic assignment

I’m working on something where i have a bunch of derived types that are extended from an abstract type. I want to be able to do a copy between concrete types (obviously of the same type). Each type has a mix of different component types, some are just scalars of intrinsic type, some have allocatable arrays and deferred strings, others have other derived types as components, and some have all of the above. Intrinsic assignment appears to be the path of least resistance but I have the following questions.

  1. Just how reliable is intrinsic assignment for the mix of components I described above
  2. Are there known problems with some compilers. Hopefully 30 years after Fortran 90 there aren’t any but Fortran is gonna Fortran.

I’m trying to avoid writing my own routines that will explicitly assign each individual component and overloading the assignment operator since that will potentially add several hundred if not thousands of lines of code to an already (overly) large module.

Just looking for feedback from others about their experiences with derived type intrinsic assignment.

Thanks

Avoid defined assignment as much as possible

  • is a good coding practice (GCP) to consider

Outside of situations involving any notion of shallow vs deep “copy” around components of derived types that have the POINTER attribute, intrinsic assignment is the better option to pursue.

From my personal experience intrinsic assignment works fine as long as you do not have too many nested derived types. I had some strange behaviors with ifort 2020 when the depth was exceeding 3 or 4.
Recently I also had a problem with gfortran 14, not accepting intrinsic assignment when both rhs and lhs are unlimited polymorphic.
In these situations, rather than writing an unmaintainable explicit copy function I rather use memcpy.

I was not so much worried about the intrinsic components or even the allocatable components as I was about some derived types I use to create “ragged” arrays of strings and integers (ie an allocatable type with allocatable components). I changed things around to do intrinsic assignment and everything compiles without error. Of course that doesn’t mean that it will work, just that the compiler thinks it will. On another subject, why is there (or maybe there is) no way to make an extended derived type non-extensible. I really would like to just use TYPE for the passed argument and not have do use CLASS all the time. This is one reason why my personal preference would have been to not introduce the CLASS keyword at all but have a POLYMORPHIC attribute for things you really want to be polymorphic (ie TYPE, POLYMORPHIC) but I guess someone decided to be OOP you had to have a CLASS somewhere (or maybe the real reason is that CLASS requires typing fewer characters).

There is the non_overridable attribute that you can add to Type-bound procedures which would make it non extensible… But don’t quote me on this as I’m just starting to use this attribute and haven’t really fully grasped the whole consequences of using it.

Tried that but it doesn’t solve the problem where you really want to do an intrinsic assignment to the passed argument which always (at least thats my understanding) has to be a CLASS argument. What I want is something along the lines of

type, extends(parent), non-extensible :: atype_t

so I don’t have to use CLASS for the passed argument.

Edit. sorry from not to

Here is an example of the problem

subroutine asub(a1, a2)
class(atype), intent(inout) :: a1
type(atype), intent(inout) :: a2

a2 = a1 ! works
a1 = a2 ! doesnt

end subroutine asub

A poor, persevering practitioner can propose, the “God” on the standard committee will dispose, even though it has been marked in a committee paper as “Easy”:

https://j3-fortran.org/doc/year/19/19-186.txt

Interesting. The more things change the more they stay the same. O Brave New World !

Edit. I’m more than willing to go through the extra step of typing non-extensible if it means I don’t have to make the passed object a polymorphic object by default just to use type bound procedures. Don’t know what the effort would be to implement this but I think it would lead to less confusion in the cases where you want to use type bound procedures but don’t need them to be polymorphic

Using a temporary pointer a possible option…? (Compiler Explorer)

module test_m
    implicit none
    type Parent_t
        integer, allocatable :: arr(:)
    endtype
    type, extends(Parent_t) :: Child_t
    contains
        procedure :: copy
    endtype

contains
subroutine copy(this, that)
    class(Child_t), target :: this
    type(Child_t) :: that
    type(Child_t), pointer :: p

    p => this
    p = that
end
end module

program main
    use test_m
    implicit none
    type(Child_t) :: a, b

    a = Child_t(arr = [integer::])
    b = Child_t(arr = [1,2,3])

    print *, "before:"
    print *, "a = ", a% arr(:)
    print *, "b = ", b% arr(:)

    call a % copy( b )

    print *, "after:"
    print *, "a = ", a% arr(:)
    print *, "b = ", b% arr(:)
end

$ gfortran test.f90 && ./a.out
 before:
 a = 
 b =            1           2           3
 after:
 a =            1           2           3
 b =            1           2           3

The code is non-conforming, the reference to b%arr in the caller does not conform.

Wouldn’t you just use a select type construct? For example:

module atype_mod

type atype
  integer :: a, b, c
end type

contains

subroutine asub(a1, a2)
class(atype), intent(inout) :: a1
type(atype), intent(inout) :: a2

a2 = a1 ! works
select type (a1)
type is (atype)
  a1 = a2
end select

end subroutine asub

end module

Or am I missing something?

I think that would be another approach, and the third approach may be to use a “free” routine to perform “class → type” cast (I don’t know how to call it…), which may be useful if an “object-based” style is mainly used.

module test_m
    implicit none
    type Parent_t
        integer, allocatable :: arr(:)
    endtype
    type, extends(Parent_t) :: Child_t
    contains
        procedure :: copy => Child_copy
    endtype

contains
subroutine Child_copy(this, that)
    class(Child_t) :: this
    type(Child_t) :: that

    call copy(this, that)
end
subroutine copy(this, that)  !! free routine (not TBP)
    type(Child_t) :: this
    type(Child_t) :: that
    this = that
end
end module

Compiler Explorer

Could you elaborate a bit more about the details…? (I may be missing some basic understanding here, but not sure…)

Here is my as yet untested solution but it does compile with no errors.
Define two methods (type bound procedures) that 1. copy from a passed argument to a target output of the same type and 2. copy from an input argument to a passed argument. Again I have no idea yet if this works but at least it compiles. Code looks like this.

subroutine copy_to(this, that)
   class(atype_t), intent(in)   :: this
   type(atype_t), intent(out) :: that
  
    that = this

end subroutine copy_to

subroutine init_type(this, that)
   class(atype_t), intent(out)    :: this
   type(atype_t), intent(in)  :: that

   call that%copy_to(this)

end subroutine init_type   

A very legitimate question and one I asked my self is why do I need to jump through these hoops when I can just do a straight intrinsic assignment. The answer is in how and why I want to access the types this way. Basically it has to do with trying to access, copy, and intialize a large number of types held in an array of a type that holds an allocatable class component of a base (abstract) type from which I derive the other types. I’m trying to avoid SELECT TYPE like the plague due to the large number of child types I need to access.

Basically here is what I’m trying to do

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`(:)`
 end type

type, extends(base_t) :: child1_t
! a lot of data
contains
    procedure :: copyTo => copyTochild1
    Procedure :: init       => init_child1
end type
type, extends(base_t) :: child2_t
! a lot of of different data
contains
    procedure :: copyTo => copyTochild2
    Procedure :: init       => init_child2
end type

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

type(child1) :: achild1
type(child2) :: achild2
allocate(base_array(2))
allocate(base_array(1)%vals, source=achild1)
allocate(base_array(2)%vals, source=achild2)

call base_array(1)%vals%init(newchild1)
call base_array(2)%vals%init(newchild2)

I have similar code now working where the init is just replaced with the preceding allocates but I want to hide the allocates from the user as much as possible. I’m probably overthinking this and making things much more complicated than they need be but I would rather start with too much code that I can slim down that have to go back and add code when I encounter a problem I hadn’t anticipated

Yes but you have no idea how much I despise SELECT TYPE. Since I’ve been trying to do OO in Fortran the two biggest issues that I’ve found that still cause compilers to gag are CLASS(*) and SELECT TYPE issues.

Reliable in my experience.

I know gfortran had some trouble with recursive allocatable components in the past, but I think they have been resolved meanwhile. Please correct me if I’m wrong here.

I will note that with gfortran, the compiler generates “hidden” procedures for copying and initialization that get added to the types virtual table. For example using @wspector’s example: Compiler Explorer

__atype_mod_MOD___copy_atype_mod_Atype:         ; "hidden" copy procedure
        mov     rax, QWORD PTR [rdi]
        mov     QWORD PTR [rsi], rax
        mov     eax, DWORD PTR [rdi+8]
        mov     DWORD PTR [rsi+8], eax
        ret
__atype_mod_MOD_asub:                           ; asub (module subroutine)
        mov     rax, QWORD PTR [rdi]            ; copy has been optimized due to -O2 flag
        mov     rdx, QWORD PTR [rax]
        mov     eax, DWORD PTR [rax+8]
        mov     QWORD PTR [rsi], rdx
        mov     DWORD PTR [rsi+8], eax
        ret
__atype_mod_MOD___vtab_atype_mod_Atype:           ; the virtual table
        .long   66313594
        .zero   4
        .quad   12
        .quad   0
        .quad   __atype_mod_MOD___def_init_atype_mod_Atype
        .quad   __atype_mod_MOD___copy_atype_mod_Atype
        .quad   0
        .quad   0
__atype_mod_MOD___def_init_atype_mod_Atype:       ; "hidden" default initialization value
        .zero   12
1 Like

Ah - that is the part I was missing. Agree it has been very frustrating how long it has taken to get reliable F2003 OO features across a majority of compilers. Hopefully you are providing those with bugs test cases so they can fix their problems.

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

An off topic question but are there plans for a second edition of Modern Fortran Style and Usage. Its one of my “go to” books these days when I have a question about a particular modern Fortran feature.

Thanks for the nice example of doing a “plugin” I know that @arjen has an example in his FLIBS libraries. In my final working code I’ll propably replace the sourced allocation of the base_t class in my example with a MOLD and only expose the init method to users. The copy_to method is only to enable the init method to do the assignment to the polymorphic passed argument.

None. Long ago, Norm and I had talked about doing a web site with errata, etc.
But it never happened.