I am experimenting with a recursively defined derived type, where the recursion is via an allocatable component instead of a pointer. This way I hope to avoid having to program a deep copy of the structure. I used this little program:
! chkrecursive.f90 --
! Check a feature: recursively defined derived type with allocatable components
! Does assignment do a "deep" allocation?
!
program chkrecursive
implicit none
type recursive
integer :: chk
type(recursive), allocatable :: next
end type recursive
type(recursive) :: chain1, chain2
chain1%chk = 1
chain2 = chain1
chain1%chk = 2
chain2%next = chain1
chain1%chk = 3
write(*,*) 'chain1: ', chain1%chk , ' - expected: 3'
write(*,*) 'chain2: ', chain2%chk , ' - expected: 1'
write(*,*) 'chain2: ', chain2%next%chk, ' - expected: 2'
chain2%next = chain2
write(*,*) 'chain2: ', chain2%chk , ' - expected: 1'
write(*,*) 'chain2: ', chain2%next%chk , ' - expected: 1'
write(*,*) 'chain2: ', chain2%next%next%chk, ' - expected: 2'
end program chkrecursive
With Intel Fortran oneAPI I get what I expect, but gfortran (version 10.2.0 from Equation Solution on Windows) gives 1 for the last line instead of 2.
I checked my expectations rather carefully, but of course a mistake is still possible. Have I run into a bug in either compiler or is my understanding of such data structures wrong?
I have been using this recursive type pattern with ifort for several years and find it very useful and have not encountered any problems, but, sorry, I have not tried it with gfortran (yet?)
Well, in the program I intend this for I run into a mysterious problem with Intel Fortran: I overload the // operation, but Intel Fortran does not accept that, at least in that full source code, small reproducers were accepted gracefully, which makes me add the epitaph “mysterious” .
Two alternatives come to mind, but both involve descending the derived type and allocating and assigning explicitly (pointer or allocatable components). An allocatable component is attractive as it avoids memory leaks automatically (or should).
As it was said in a few topics already (one of them) support for newer features of derived types in gfortran is not complete yet. Surely PDTs, apparently also recursive DTs.
Just curious: any use case for such a construct? At first glance it looks a bit awkward to me.
We use it for a multi-dimensional population balance, outer array time, then cancer type, then size of primary tumour then secondary spread if appropriate to tumour type etc, but is incredibly flexible especially used as an extension of an abstract data type, where each new layer has procedure pointers to relevant calculation for that layer
I will report it as a bug, now that I know my expectations for this particular program are correct.
The context of my program is the Mathematics of Arrays project (see the thread in this forum). My current implementation is flawed as it can cause self-references (ending in an endless loop ) and I need to be more careful about the references. My first thought was that with allocatable recursive types this would be solved automatically, so I wrote this program to check that.
Following the discussion about problems with the recursive derived type:
First, the Arjen issue is still present with gfortran-15.
Second, I’ve encountered another problem when using OpenMP.
I’ve modified Arjen code’s by adding an OpenMP block and making some simplifications.
program chkrecursive
implicit none
type recu_t
integer :: chk
type(recu_t), allocatable :: next ! with fortran: compilation fatal error with OpenMP.
! With ifx: the compilation does not stop with OpenMP!!
!type(recu_t), pointer :: next => null() ! compilation and execution ok with gfortran and ifx
end type recu_t
type(recu_t) :: chain2
!$OMP PARALLEL &
!$OMP DEFAULT(NONE) &
!$OMP PRIVATE(chain2)
chain2%chk=0
write(*,*) 'chain2%chk',chain2%chk
!$OMP END PARALLEL
end program chkrecursive
I’ve did the following tests:
With gfortran (version 14 and 15) with OpenMP, on an arm64 mac and on linux
=> gfortran: fatal error
With ifx (2024) and with OpenMP, on linux
=> the compilation does not stop!!
With ifort (2024) and with OpenMP, on linux
=> The compilation and the execution work as expected
With nagfor
=> The compilation and the execution work as expected
When the “allocatable” (line 10) is replaced by a “pointer” (line 11) or when the OpenMP is turned off. The compilation and the execution work as expected for all compilers.
Wouldn’t it be better to submit this as a bug-report directly to the GCC “Bugzilla” page?
I am seeing people repeatedly posting compiler bugs here on the forum, when it would be far more appropriate to submit them to the compiler developers directly (via the known channels).
Now, this still doesn’t always guarantee that the bug will indeed get fixed (as the OP’s original problem seems to demonstrate). But your chances to obtain a bug-fix are essentially null, if you are hesitant to inform the developers directly.
If it’s a bug in many compilers, then I think it’s good to post here as a centralized discussion and then report into individual compilers with a link here for more details.
I agree with the previous posters: I think the posted code is correct and it exposes a compiler bug. Just out of curiosity, I added a temp variable and changed the above statement to
temp = chain2
chain2%next = temp
and gfortran produces correct output. I also added the allocatable attribute to the temp declaration, and
and this produces the correct output. To my eye, this sequence should produce the same instructions as the original assignment statement, it is just more verbose.
One other workaround I tried was to add the allocatable attribute to the chain2 declaration, and this still produced incorrect results with the original assignment statement. So it seems that the problem with gfortran occurs when the rhs and lhs of the assignment involve the same variable.
Yes, of course, this is more or less the standard way in fortran to declare a linked list. A simple linked list like this is useful when the final number of members is unknown and arbitrarily large, when they are added one at a time to the list, and when the list is always scanned from last to first. If you add a pointer component to the derived type, then you can scan the list in both directions while still retaining the advantages of an allocatable member over pointer allocation. Other data structures, such as binary search trees, or more general data networks, use the same kind of approach.