Unlimited polymorphic entities
Last updated on 2026-07-31 | Edit this page
Overview
Questions
- How does Fortran support unknown data (at compile time) types?
Objectives
- Understand the use of unlimited polymorphic entities
- Implement a data structure capable of holding arbitrary data based on polymorphic entities
It is sometimes useful in C to be able to use a void *
pointer, a pointer for which there is no type-checking at compile time.
The nearest analogue in Fortran is the unlimited polymorphic
pointer.
class (*) pointers
We have seen a polymorphic pointer of given class used in the context of type extension. A more general type of pointer, an unlimited polymorphic pointer can be declared as
This is particularly useful if a polymorphic reference of intrinsic types (which cannot be extended) is required. For example:
FORTRAN
real (real32), target :: r32
real (real64), target :: r64
real (real32), pointer :: p32 => null()
real (real64), pointer :: p64 => null()
class (*), pointer :: p => null()
p32 => r32 ! ok
p64 => r32 ! compile-time error
p => r32 ! ok
p => r64 ! ok
While we cannot associate a pointer of a fixed type
(p64) with a target of incompatible type, we can with an
unlimited polymorphic pointer.
However, unlike the type-specific pointer, we cannot use the unlimited polymorphic pointer in any context, e.g.:
Pointer assignments are valid
If an unlimited polymorphic pointer is on the right-hand side of an assignment, then the left-hand side must be a pointer to a non-extensible derived type. E.g.,
is not valid, as this allows an association to an incompatible type.
Exercise (2 minutes)
Compiling pointers
The accompanying code example1.f90 has three invalid
assignments which will not compile, and one additional error. Check the
compiler messages for each.
OUTPUT
example1.f90:14:2:
14 | p32 => r64
| 1
Error: Different types in pointer assignment at (1); attempted assignment of REAL(8) to REAL(4)
example1.f90:19:2:
19 | p = 0.0
| 1
Error: Nonallocatable variable must not be polymorphic in intrinsic assignment at (1) - check that there is a matching specific subroutine for '=' operator
example1.f90:21:2:
21 | p32 => p
| 1
Error: Data-pointer-object at (1) must be unlimited polymorphic, or of a type with the BIND or SEQUENCE attribute, to be compatible with an unlimited polymorphic target
example1.f90:24:23:
24 | print *, "Result ", p
| 1
Error: Data transfer element at (1) cannot be polymorphic unless it is processed by a defined input/output procedure
Typed allocation and sourced allocation
Unlimited polymorphic pointers may be used as actual arguments to
procedures. One relevant intrinsic case is allocate().
Typed allocation has a form similar to a constructor which specifies the
type:
This allows allocations against unlimited polymorphic pointers, e.g.,
FORTRAN
class (*), pointer :: p => null()
class (*), pointer :: r(:) => null()
allocate(character (len = 9) :: p)
allocate(real (real32) :: r(7))
A useful step in many circumstances is to use sourced allocation:
which will produce a copy of a; p will take
on the dynamic type of a.
Exercise (20 minutes)
A key value pair
The accompanying template module key_value_module.f90
provides a derived type that is intended to store key value pairs, where
the key is a (deferred length) string, and the value is an unlimited
polymorphic pointer.
FORTRAN
type, public :: key_value_t
character (len = :), allocatable :: key
class (*), pointer :: val
end type key_value_t
In principle, this can store values of any type.
Implement two specific constructors to establish key-value pairs for
integer and real intrinsic types (int32 and
real32 from iso_fortran_env), using
key_value_create_str() as an example. Each should allocate
appropriate memory for the key and the value. These should overload the
default structure constructor key_value_t().
Implement a subroutine key_value_print() which uses a
select type construct to display the current type, key and
value of the three different data types.
To be complete, implement a routine to release the resources
associated with a key_value_t. We could use type-bound
procedures for these last two operations, but it’s not really necessary
in this context.
The accompanying program has some examples to act as a test.
The constructors for int32 and real32 could
look like
FORTRAN
function key_value_create_i32(key, ivalue) result(kv)
character (len = *), intent(in) :: key
integer (int32), intent(in) :: ivalue
type (key_value_t) :: kv
kv%key = trim(key)
allocate(kv%val, source = ivalue)
end function key_value_create_i32
and
FORTRAN
function key_value_create_r32(key, rvalue) result(kv)
character (len = *), intent(in) :: key
real (real32), intent(in) :: rvalue
type (key_value_t) :: kv
kv%key = trim(key)
allocate(kv%val, source = rvalue)
end function key_value_create_r32
The generic interface for key_value_t will need to
contain all three specific constructors:
FORTRAN
interface key_value_t
module procedure key_value_create_i32
module procedure key_value_create_r32
module procedure key_value_create_str
end interface key_value_t
To print the data stored in a key_value_t:
FORTRAN
subroutine key_value_print(kv)
type (key_value_t), intent(in) :: kv
select type (val => kv%val)
type is (integer (int32))
print *, "int32 kv ", kv%key, val
type is (real (real32))
print *, "real32 kv ", kv%key, val
type is (character (len = *))
print *, "string kv ", kv%key, val
type is (real (real64))
print *, "real64 kv ", kv%key, val
class default
print *, "value type not recognised kv", kv%key
end select
end subroutine key_value_print
Finally, to release a key_value_t you could do the
following, noting that memory for both the value and the key itself
should be released with allocate():
A dynamic list of key value pairs
It might be useful to have an expandable list of such key value
pairs. A possible implementation is also provided in the
key_value_module.f90
The list constructor and a procedure to add a
key_value_t are provided. There’s also a routine to print
out the list contents. What remains to be provided is a subroutine which
increases the storage as required. This should use
move_alloc(). Have a go at providing this subroutine.
Your subroutine might look like the following:
FORTRAN
subroutine key_value_list_reallocate(kvlist)
! Expand list storage (by a factor of 2)
type (key_value_list_t), intent(inout) :: kvlist
type (key_value_t), allocatable :: kvtmp(:)
integer :: nnew
! The list must be initialised here ...
if (.not. allocated(kvlist%kv)) stop "list not initialised!"
nnew = max(1, 2*size(kvlist%kv))
allocate(kvtmp(nnew))
kvtmp(1:kvlist%npair) = kvlist%kv(1:kvlist%npair)
call move_alloc(kvtmp, kvlist%kv)
end subroutine key_value_list_reallocate
- Unlimited polymorphic entities provide a
void *-like type in Fortran - Without determining the unlimite polymorphic entity’s dynamic type, it cannot be used