Type extension and polymorphism
Last updated on 2026-07-10 | Edit this page
Estimated time: 35 minutes
Overview
Questions
- How can we reuse type definitions in related types?
- How can we write generic procedures for related types?
- How can we apply type-specific behaviour for related types?
Objectives
- Understand type extension
- Understand the use of polymorphism
- Understand declared and dynamic types
Type extension and polymorphism provide the ability to define a uniform interface which can be used to interact with different implementations, and also handle references to such objects in a uniform way. This is the basis for abstraction.
Extending an existing type
Suppose we had a type to represent a general category of entity or object, e.g.,
type, public :: object_t
real :: rho ! density
real :: x(3) ! position of centre of mass
end type base_t
We can now define a more specific entity by extending the
object_t, e.g.,
type, extends(object_t), public :: sphere_t
real :: a ! radius
end type sphere_t
This new type is said to inherit the components of the original type which may be accessed via the component selector in one of two ways:
type (sphere_t) :: s
real :: density
density = s%object_t%rho
density = s%rho
The two assignments are equivalent. The new type has a parent component which has the name of the original, or base, type.
For operations which treat the base type as a whole, the longer form may be useful. However, if just component access is required, the second, shorter, form is more concise and to be preferred.
Single inheritance
A new type may extend only one existing type in Fortran: it has a so-called single inheritance model.
Structure constructors
There is a default structure constructor associated with the new type
type (sphere_t) :: s
real :: rho = 1.0
real :: x(3) = [ 2.0, 3.0, 4.0 ]
real :: radius = 1.5
s = sphere_t(rho, x, radius)
The order for the components in the structure constructor is the base type components first, and then the extended type components second. The components of each type also appear in the order that they have been declared.
The order can be adjusted with the use of keywords, e.g.:
s = sphere_t(a = radius, rho = rho, x = x)
We may also use the base type as a component of the structure constructor
type (object_t) :: obj
type (sphere_t) :: s
real :: a = 2.5
obj = object_t(1.5, [1.0, 1.0, 1.0])
s = sphere_t(object_t = obj, a)
Keywords are not necessary unless the order of the arguments is other than the defined order. Recall that components with default initialisations may be omitted in the structure constructor.
New types can be further extended by following the same procedure.
Example (5 minutes)
Extending types
Further extend the sphere_t to give a
charged_sphere_t by adding a (real) component
q in the new type. The first two type definitions are
provided in the file object_type.f90.
type, extends(sphere_t), public :: charged_sphere_t
real :: q = -1.0 ! charge
end type charged_sphere_t
Confirm you can compile the code with
ftn example1.f90 object_type.f90
Extending types (continued)
In the example main program, check you can provide some value for the
new charge component via a constructor (e.g., q = -1.0),
and access the ancestor components of the new type in both long and
short forms.
! ... Earlier declarations ...
cs = charged_sphere_t(s, 5.0)
print *, "Charged sphere q = ", cs%q
print *, "Charged sphere density (short) = ", cs%rho
print *, "Charged sphere density (long) = ", cs%sphere_t%object_t%rho
Compiling and running the code you should obtain output similar to
$ ftn example1.f90 object_type.f90 && ./a.out
Sphere density 1.00000000
Sphere density 1.00000000
Sphere radius 1.50000000
Sphere density 1.00000000
Sphere density 1.00000000
Sphere radius 1.50000000
Sphere density 2.50000000
Sphere position: 1.00000000 1.00000000 1.00000000
Sphere radius 1.50000000
Charged sphere q = 5.00000000
Charged sphere density (short) = 2.50000000
Charged sphere density (long) = 2.50000000
Polymorphism
In order to be able to handle types and extended types in a flexible way, we need a mechanism that allows a given variable to reference objects of different type. Such a variable is typically a pointer in many languages.
Fortran provides the pointer mechanism using
class (object_t), pointer :: obj => null()
One may also have an allocatable polymorphic object:
class (object_t), allocatable :: obj
A class pointer cannot be declared as being of an intrinsic type: the object must be an extensible derived type.
The class declaration is a signal that we intend to use
this variable in a polymorphic context. We will consider just the
pointer alternative for the time being.
Declared and dynamic type
The declaration
class (object_t), pointer :: obj => null()
allows the pointer obj to be associated with a target
which has a type object_t or any other type which extends
object_t. The pointer is said to be
type-compatible with this class of objects.
In the above example, the declared type of the polymorphic
pointer is object_t. The declared type does not change.
The dynamic type may be changed by associating the pointer with a target of an extended type, e.g. with:
class (object_t), pointer :: obj => null()
type (sphere_t), target :: s
obj => s
the dynamic type would become sphere_t. The dynamic type
of an unassociated pointer is its declared type.
The polymorphic variable has access to components of only of the declared type, but not of its descendents. So
class (object_t), pointer :: obj => null()
type (sphere_t), target :: s
obj => s
obj%rho ! ok: rho is a component of the declared type
obj%a ! erroneous: radius `a` is component of the extended type
Polymorphic arguments
Dynamic type will also be relevant when procedures are considered: polymorphic dummy arguments take on the dynamic type of the associated actual argument.
Exercise (5 minutes)
Declared vs dynamic type
Compile the second example together with your updated
object_type.f90 which includes a
charged_sphere_t:
$ ftn example2.f90 object_type.f90
(or use the canned solution object_types.f90).
Attempting to compile this code should fail with errors
$example2.f90:20:34:
20 | print *, "object radius ", p%a
| 1
Error: 'a' at (1) is not a member of the 'object_t' structure
example2.f90:26:34:
26 | print *, "sphere radius ", p%a
| 1
Error: 'a' at (1) is not a member of the 'object_t' structure
example2.f90:29:34:
29 | print *, "sphere charge ", p%q
| 1
Error: 'q' at (1) is not a member of the 'object_t' structure gfortran exercise2.f90 object_type.f90
Declared vs dynamic type (continued)
Comment out (don’t remove for the time being) the erroneous statements from the example so it will compile.
Why can’t we just declare the pointer to be:
class (charged_sphere_t), pointer :: p
in this example? What is the compiler error if you try?
The charged_sphere_t is higher in the inheritance chain
than object_t, it therefore cannot be associated with the
base derived type.
example2.f90:16:2:
16 | p => obj
| 1
Error: Different types in pointer assignment at (1); attempted assignment of TYPE(object_t) to
CLASS(sphere_t)
Type selection
Code may detect the dynamic type of a polymorphic variable via use of
the select type construct, which allows appropriate action
to to taken depending on the dynamic type. This is similar to the simple
select case construct, where the behaviour is controlled by
the dynamic type of the selector, here p:
select type (p)
type is (charged_sphere_t)
print *, "Charge is ", p%q
class is (sphere_t)
print *, "Radius is ", p%a
class default
print *, "bare object_t"
end select
There are two forms of the so-called type guard statement:
type is and class is. They may be combined in
a single construct. The result depends on the following logic:
- if the dynamic type of the selector exactly matches a
type isblock, then that block is executed; - otherwise, if the dynamic type matches a
class isblock (i.e., it matches that type or a descendant) theclass isblock is executed; - otherwise, the
defaultblock is executed (if present).
So at most one block is executed for any given selector.
Type inquiry functions
There are a number of intrinsic type inquiry functions which take polymorphic arguments, and return a logical result depending on dynamic type.
extends_type_of(a, b)
returns .true. if the dynamic type of a is
an extension of b; and
same_type_as(a, b)
returns .true. if the dynamic types of both arguments
are equal.
Exercise (10 minutes)
Type selection
Write a subroutine in object_type.f90 which takes a
single polymorphic argument of object_t, and prints out all
the relevant components depending on the dynamic type of the actual
argument.
The body of the subroutine will look like
print *, "Select type: "
select type (p)
class is (object_t)
print *, "density ", p%rho
print *, "position ", p%x(:)
class is (sphere_t)
print *, "sphere radius ", p%a
class is (charged_sphere_t)
print *, "sphere charge ", p%q
class default
print *, "unknown"
end select
Type selection (continued)
Check your subroutine works by passing each different type in turn
from the example2.f90 program.
(Optional) Type constructors again
Write some generic constructors for object types which take different data types as arguments. For example, it might be a convenience to be able to specify the position as a three-vector of integers.
A possible implementation might be of the form
subroutine object_info(p)
class(object_t), pointer, intent(in) :: p
select type (p)
type is (charged_sphere_t)
print *, "Charge = ", p%q
class is (sphere_t)
print *, "Radius = ", p%a
class is (object_t)
print *, "Density = ", p%rho
print *, "Position = ", p%x
class default
print *, "Unknown object type!"
end select
end subroutine object_info
- Fortran supports polymorphism through the
classpointers and allocatable objects. - Types can be selected dynamically to support type-specific behaviours