Version: XE Studio 2015, update 6
I'm trying to write a function with the following specification
So obviously that last point is a tall order. As far as I can see, the typical approach is just to duplicate the function for all data types it will be used with. Coming from Python, the idea of doing this causes me great pain.
As far as I know, you can't set the kind of something like an integer at runtime. But you can set aside a block of memory equal to that occupied by the datatype by allocating array as if it were an array of strings, e.g.
allocate(character(len=memsize) :: array(abs(length)))
You can also use NO_ARG_CHECk to suppress compiler warnings when you try to feed in integer arrays etc.
I find that my function works a lot of the time, but lets say 50% of the time, it will do something like this
!Up in definitions type(COTYPE), allocatable :: co(:) !Somewhere down in the bowels of the code !Part of a loop in which the first attempt is much more likely to succeed !The second go around, the code still has a chance but it's rare that it works l = ReDimPreserve(co,-1,sizeof(co(1))) !Add one to the length of this array if (allocated(co(ubound(co,1))%VolHistory)) then print "Allocated" else print "Not allocated" end if allocate(co(ubound(co,1))%VolHistory(1))
> Not allocated forrtl: severe (151): allocatable array is already allocated Image PC Routine Line Source libifcoremd.dll 000000000059E478 Unknown Unknown Unknown libifcoremd.dll 00000000005985A9 Unknown Unknown Unknown libifcoremd.dll 0000000000584E9F Unknown Unknown Unknown libifcoremd.dll 00000000004F5198 Unknown Unknown Unknown libifcoremd.dll 0000000000548600 Unknown Unknown Unknown usub.exe 000000013FB8E469 JAMESMOD_mp_SETUP 684 usub.f
As can be seen, we satisfied ourselves that VolHistory was definitely not allocated, and then immediately execution failed when trying to allocate it, saying it's already allocated.
This kind of error seems to happen a lot and since it's so intermittent it obviously has something to do with not correctly managing the memory on my part . I would really like to know why it doesn't work 100% of the time, especially if it works extremely well some of the time.
On the times that it does work, you can put data in VolHistory, you can redimension co to be longer or shorter, you can also redimension VolHistory in earlier instances of COTYPE in co and I've not once seen it loose or corrupt data. When it works, it's surprisingly robust.
What is actually happening in memory when I use this approach? Is there any possible way of making it work reliably since I'm so close?
type COTYPE sequence integer reference real*8, allocatable :: VolHistory(:) end type
integer function ReDimPreserve(array, length, memsize) !DEC$ATTRIBUTES NO_ARG_CHECK :: array implicit none integer, value :: length, memsize integer :: assignBound character(len=:), allocatable :: array(:) character(len=:), allocatable :: tempArray(:) if (not(allocated(array))) then allocate(character(len=memsize) :: array(abs(length))) ReDimPreserve = abs(length) return end if if (length < 0) then length = ubound(array,1) + abs(length) end if ReDimPreserve = length assignBound = min(length,ubound(array,1)) allocate(character(len=memsize) :: tempArray(1:length)) tempArray(1:assignBound) = array(1:assignBound) call move_alloc(FROM=tempArray,TO=array) end function ReDimPreserve
I decided to go with a different approach, using the Fortran Pre Processor, which I'm happy with.
(On another note, I'm not sure when ';' was added to support multi-line preprocessor definitions, I couldn't find any hint of this and ultimately got it through brute force trial and error.)
#define __SupportReDimPreserve__( __TYPE__ , __ALIAS__ ) \ function __ALIAS__(array, length) result (RDP) ; \ ; \ implicit none ; \ __TYPE__ , allocatable :: array(:) ; \ __TYPE__ , allocatable :: tempArray(:) ; \ integer, value :: length ; \ integer :: assignBound, RDP ; \ ; \ if (not(allocated(array))) then ; \ allocate(array(abs(length))) ; \ RDP = abs(length) ; \ return ; \ end if ; \ ; \ if (length < 0) then ; \ length = ubound(array,1) + abs(length) ; \ end if ; \ ; \ RDP = length ; \ assignBound = min(length,ubound(array,1)) ; \ allocate(tempArray(1:length)) ; \ tempArray(1:assignBound) = array(1:assignBound) ; \ call move_alloc(FROM=tempArray,TO=array) ; \ ; \ end function __ALIAS__ __SupportReDimPreserve__(Integer, RDP_Int) __SupportReDimPreserve__(real(8), RDP_Real) __SupportReDimPreserve__(logical, RDP_Logical)
Then somewhere up top
interface ReDimPreserve module procedure RDP_Int, RDP_Real, RDP_Logical end interface ReDimPreserve
I think this solution is far more compiler safe than my previous approach. Its very neat and solves the problem of maintaining several blocks of pretty much identical code.
In your original post, you wrote, "The function supports all 1D arrays of any datatype (intrinsic or user)" - see the thread below re: possible performance hit with "user" type:
As you have observed, there are limitations when it comes to generic programming in Fortran. I personally prefer to avoid preprocessors but to each their own, of course. If I needed something like this, I would consider standard Fortran with 'CLASS(*), ALLOCATABLE' dummy arguments and I would likely restrict the function to *any intrinsic data type* plus *any derived type that extends from an arbitrary abstract type' that implements certain 'MOVE' semantics based on the thread I link above.
Hope you have continued success with Fortran to build on your work with Python,