I'd like to realize useful array operations (add element, remove element, different realizations by allocatable/pointer/binary tree structures) by class(*)
feature (unlimited polymorphism). I use gfortran 5.0 that should handle such a feature. I need it for not repeating identical code for each type I use.
This should look something like
function add_element(array,element)
class(*),intent(in)::array(:)
class(*),intent(in)::element
class(*)::add_element(size(array)+1)
add_element=[array,element]
end function
The problem is that when I try to use this function with some definite type, I have an error with returning result. I can not assign class(*)
to some definite type variable without select type
, and I surely don't want to have select type structures every time I use it. And inside a subroutine I should not know anything of types I want to use, because I will create many of them.
I tried some variants with move_alloc
, source, tried to use subroutine with intent(out)
argument etc. It didn't work. I think it should be defined in argument attributes, the same as size (with source keyword?) but didn't find an example or a definition of such structure in standard. Of course I will study this standard more (I'm not a professional programmer but physicist trying to make my programs testable, checkable and more comfortable to change) and will simply repeat this code now in waiting for better solution, but maybe anybody knows where to search it in the standard or some book? I think this is not only about arrays but use of class(*)
at all as I think there should be methods that don't know of types...
Don't know if I should add examples of other not working forms of this subroutine or what it says about the error - or a question will be unfocused. It can be compiled, but in all cases assigning to definite type in call doesn't work. For argument intent(out)
or (inout)
it can not go from dummy argument to actual argument. Reallocation from source makes an object which has type (and a result of assigning in my example too), but the type is hidden... and I can't use the select type in subroutine because I don't know the type.
Also I don't know constructs that could check "the same type as" or something in this context...
This is not an easy problem You can use select type
, but Fortran doesn't have anything like type is(type_of(x))
. On the other hand, there are the SAME_TYPE_AS()
and EXTENDS
TYPE_OF()
intrinsics, but you cannot use them as type guards.
It is necessary to assure, that the dynamic types of both array
and element
are the same.
I think this is a deficiency in the standard.
But still, there is an error in your approach. You should make the function result allocatable, to be able to allocate it to correct dynamic type:
class(*), allocatable ::add_element(:)
You may think something along the lines of: (UNTESTED! compiles with gfortran-4.9 ifort14)
allocate(add_element(size(array)+1), mold=array)
But how to actually transfer the values I don't know and I am worried it might not be possible without resorting to some dirty tricks.
You cannot even use transfer
and that is where I see real deficiency. Eventhough you can call transfer with polymorphic mold
transfer(element, add_element(1))
you have no way to assign it to the array element
add_element(1) = transfer(element, add_element(1))
My opinion is that Fortran lacks an option for the type guards that just ensures that two variables have the same dynamic type.
You may think something along the lines of: (UNTESTED! compiles with gfortran-4.9 ifort14)
function add_element(array,element)
use iso_c_binding
implicit none
class(*),intent(in)::array(:)
class(*),intent(in)::element
class(*), allocatable ::add_element(:)
type(c_ptr) :: tmp
interface
function memcpy(dest, src, n) bind(c)
use iso_c_binding
integer(c_intptr_t),value :: dest, src
integer(c_size_t) :: n
type(c_ptr) :: memcpy
end function
end interface
allocate(add_element(size(array)+1), mold=array)
tmp = memcpy(loc(add_element(size(array)+1)), &
loc(array), &
size(array, kind=c_size_t) * storage_size(array, c_size_t)/8_c_size_t )
tmp = memcpy(loc(add_element(size(array)+1)), &
loc(array(1)), &
storage_size(element, c_size_t)/8_c_size_t )
end function
CLASS(*) is a facility that basically allows runtime type safe but type agnostic storage. You are trying to use it as a compile time type parameterisation mechanism. It isn't terribly appropriate for that, and the language doesn't directly support an alternative means.
Traditionally type parameterisation is done by placing the common parts of the procedures to be parameterised in a separate file, and then including that file as appropriate, perhaps in a module that uses implicit typing to specify the type to be parameterised.
If you must use CLASS(*), you practically need to write and use a wrapper type. If all you are wrapping is basic array operations, then this will be far more trouble than it is worth.
In client code (versus your common procedures) to extract the thing that has been stored you generally need to use SELECT TYPE (you can use pointer assignment if the type of the data has BIND(C) or SEQUENCE, but this isn't type safe).
TYPE :: Wrapper
CLASS(*), ALLOCATABLE :: item
END TYPE Wrapper
FUNCTION add_element(array, element)
TYPE(Wrapper), INTENT(IN) :: array(:)
CLASS(*), INTENT(IN) :: element
TYPE(Wrapper), INTENT(OUT) :: add_element(SIZE(array)+1)
! If you want to enforce type consistency (at runtime)...
IF (SIZE(array) > 0) THEN
IF (.NOT. SAME_TYPE_AS(array(1)%item, element)) THEN
STOP 'Objects not of same type!'
END IF
END IF
add_element(:SIZE(array)) = array
add_element(SIZE(add_element))%item = element
END FUNCTION add_element
FUNCTION get(scalar)
TYPE(Wrapper), INTENT(IN) :: scalar
CLASS(*), ALLOCATABLE :: get
get = scalar%item
END FUNCTION get
...
TYPE(Wrapper), ALLOCATABLE :: array(:)
array = [ Wrapper :: ]
array = add_element(array, 'cat')
array = add_element(array, 'dog')
DO i = 1, SIZE(array)
SELECT TYPE (item => get(array(i)))
TYPE IS (CHARACTER(*))
PRINT "(A)", item
END SELECT
END DO