Parameterized Derived Types
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and was checked on 2026-10-19. Every
example was compiled with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2)
and run, and, where the feature is portable, also built and run with LLVM Flang 23; the output shown is real output.
Examples that are deliberately wrong, that crash a compiler or that expose a difference between the compilers are
labelled as such and show the real diagnostics. The rules are in 7.2 (type parameters), 7.5.3 (derived-type
parameters, with the type parameter order in 7.5.3.2), 7.5.9 (derived-type specifier), 7.5.10 (construction of
derived-type values) and 9.4.5 (type parameter inquiry); the places where a type parameter changes another statement
are 9.7.1.2 (allocate), 10.2.1.2 and 10.2.1.3 (intrinsic assignment), 11.1.11 (select type), 15.4.2.2 (explicit
interfaces), 15.5.2.5 (dummy arguments) and 15.5.5.2 (generic resolution).
A parameterized derived type (PDT) is a derived type whose definition takes parameters, in the way the intrinsic
types real and character take kind and len. A parameter is either a kind parameter, a compile-time constant
that selects a different type instance, or a length parameter, a value that sizes components and may be fixed at run
time. They exist so that one definition can describe a family of types, such as a temperature field of any precision
and any extent, without copying the source or storing the sizes by hand. The running example is the temperature field
of heatflow, where the grid and the field it carries are the natural candidates.
This page is also candid about the state of the compilers, because PDTs are the part of the language where the two
free compilers differ most. LLVM Flang 23 accepts kind parameters completely and stops with "not yet implemented" on
the first length parameter. GNU Fortran 16.2 accepts both kinds of parameter in many programs, but this page records
the defects found while writing it: wrong results, silent acceptance of invalid programs, false errors on valid ones,
two segmentation faults and an internal compiler error. The section Compiler support and practical limits lists them with the
files that reproduce them and gives the subset that works in both. Because of that, the heatflow library itself does
not use PDTs: its grid_t keeps the extents in plain integer components and the temperatures in an allocatable array,
and its precision is the named constant dp from heatflow_kinds. The reasons are in
PDTs or something simpler.
The neighbouring pages cover derived types in general, in
Derived Types, type extension and polymorphism in
Object Oriented Programming, bindings and
finalization in
Type-Bound Procedures, Abstract Types and Finalization,
the generic machinery used by the kind-based overloads in
Interfaces, Generics and Operator Overloading,
and the kind and len of the intrinsic types in
Intrinsic Types and Kinds.
Kind and length parameters
Every type can have type parameters (7.2). The intrinsic types have fixed ones: each has a kind parameter named
kind, and character also has a length parameter named len. A derived type has whatever parameters its
definition lists. All type parameters are integers, and each is either a kind parameter or a length parameter
(R734). The two classes differ in when the value must be known and in what the parameter may be used for:
| Property | kind parameter |
len parameter |
|---|---|---|
Value of the actual parameter |
a constant expression, so known at compile time (C701) |
any scalar integer expression, so possibly different at each execution |
Allowed as |
no, because those are not constant expressions |
yes (C702 for |
Used in the definition |
as a primary of a constant expression and of a specification expression (7.5.3.1), so it can select the kind of a component |
as a primary of a specification expression only, so it can size an array bound or a character length |
Takes part in generic resolution |
yes (15.5.5.2): two specifics may differ only in a kind parameter |
no (7.2): two specifics that differ only in a length parameter are ambiguous |
Assignment between instances |
the values must be equal (10.2.1.2) |
the values must be equal, unless the target is allocatable with a deferred parameter (10.2.1.2) |
Distinct type for the compiler |
yes, |
no, the same type with another layout (see the Flang design notes in the references) |
The distinction mirrors the intrinsic types. real(sp) and real(dp) are different types, so a real(sp) argument
cannot be passed to a real(dp) dummy, and the choice of the kind is made when the program is compiled. A character(len=n)
value, on the other hand, may have a length known only when the procedure runs, and every length is compatible with the
others through blank padding. A PDT’s length parameter is closer to an automatic array’s extent than to a character
length, because PDTs, unlike characters, do not convert between lengths in assignment (see Assignment, argument association and interfaces).
The type parameters of the intrinsic types can be inquired about with the same syntax as those of a PDT, x%kind and
s%len (9.4.5, see Type parameter inquiry), which is the reason the syntax was chosen.
Declaring a parameterized type
The type parameters are listed in parentheses after the type name (R727), and each one is then declared in a
type-param-def-stmt (R732) that gives its class and, optionally, a default value (R733). The statements precede the
components. The example is a temperature field with the precision as a kind parameter and the two extents as length
parameters:
module field_mod
use, intrinsic :: iso_fortran_env, only: real32, real64
implicit none
private
public :: sp, dp, field_t
integer, parameter :: sp = real32
integer, parameter :: dp = real64
! A temperature field whose precision (kind) and size (len) are type parameters.
type :: field_t(wp, nx, ny)
integer, kind :: wp = dp ! kind parameter: a constant expression, default dp
integer, len :: nx, ny ! length parameters: may be run-time values
real(wp) :: u(nx, ny) ! component sized by the length parameters
end type field_t
end module field_mod
program pdt_basics
use, intrinsic :: iso_fortran_env, only: output_unit
use field_mod, only: sp, dp, field_t
implicit none
integer :: n
type(field_t(dp, 4, 3)) :: a ! positional: wp=dp, nx=4, ny=3
type(field_t(nx=5, ny=2)) :: b ! keywords; wp takes its default, dp
type(field_t(wp=sp, nx=2, ny=2)) :: c
n = 6
block
type(field_t(dp, n, n + 1)) :: d ! length parameters may be run-time expressions
d%u(2, 3) = 100.0_dp
print '(a, 2i3, a, i0, a, f6.1)', 'd%nx, d%ny =', d%nx, d%ny, ' size(d%u) = ', size(d%u), &
' d%u(2,3) = ', d%u(2, 3)
end block
a%u(1, 1) = 20.0_dp
print '(a, i0, 1x, i0, 1x, i0)', 'a%wp, a%nx, a%ny: ', a%wp, a%nx, a%ny
print '(a, i0, 1x, i0, 1x, i0)', 'b%wp, b%nx, b%ny: ', b%wp, b%nx, b%ny
print '(a, i0, 1x, i0, 1x, i0)', 'c%wp, c%nx, c%ny: ', c%wp, c%nx, c%ny
print '(a, i0, a, i0, a, i0)', 'shape(a%u) = ', size(a%u, 1), ' x ', size(a%u, 2), '; kind(c%u) = ', kind(c%u)
flush (output_unit)
end program pdt_basics
Output:
d%nx, d%ny = 6 7 size(d%u) = 42 d%u(2,3) = 100.0
a%wp, a%nx, a%ny: 8 4 3
b%wp, b%nx, b%ny: 8 5 2
c%wp, c%nx, c%ny: 4 2 2
shape(a%u) = 4 x 3; kind(c%u) = 4
GNU Fortran 16.2 builds and runs this program. LLVM Flang 23 accepts it in its semantic checks, then stops in code generation because of the length parameters:
error: loc("/work/01_basics.f90":11:11): flang/lib/Lower/ConvertType.cpp:470: not yet implemented: parameterized derived types
LLVM ERROR: aborting
The rules in the definition:
| Rule | Detail | Clause |
|---|---|---|
Every parameter has a class |
each name in the parenthesised list appears exactly once in a |
7.5.3.1 |
A parameter is an integer |
the kind selector of the |
7.5.3.1 |
A default value is a constant |
|
7.5.3.1, 7.5.9 |
Where the parameters can be used |
as primaries in specification expressions of the definition (array bounds, character lengths); a kind parameter additionally in constant expressions, for example |
7.5.3.1 |
A parameter is not a component |
a type parameter inquiry is not a variable and cannot be assigned to |
9.4.5 |
The type parameters of a component |
a |
7.5.4.1 |
The example puts the defaults in the right place for the heatflow field: wp defaults to dp, so type(field_t(nx=5, ny=2))
is a double-precision field, and the two extents have no default, so every declaration has to give them.
Type parameter order, defaults and the derived-type specifier
Wherever a parameterized type is named in a declaration, an allocate type-spec, a select type guard or a structure
constructor, it is written as a derived-type specifier: the type name followed by an optional parenthesised list of
type-param-spec entries (R754, R755). The list obeys the same rules as an actual argument list with keywords:
-
A value without a keyword corresponds to the next parameter in type parameter order (7.5.3.2), which is the order of the names in the first line of the definition. For an extended type, the parent’s parameters come first in the parent’s order, followed by the extension’s own (7.5.3.2).
-
Once a keyword appears, every later entry needs a keyword (C798), and each keyword must be the name of a parameter (C799).
-
At most one value per parameter (C797). A parameter that is not mentioned takes its default value, and one without a default must be given (C797).
-
The list may appear only if the type is parameterized (C796), and a value is converted by intrinsic assignment rules to the kind of the parameter (7.5.9).
program pdt_order
use, intrinsic :: iso_fortran_env, only: real32, real64
implicit none
integer, parameter :: sp = real32, dp = real64
type :: probe_t(wp, n)
integer, kind :: wp = dp
integer, len :: n = 3 ! a default for a length parameter as well
real(wp) :: reading(n)
character(len=n) :: tag
end type probe_t
! Extension: the parent's parameters come first in the parameter order, then the new ones.
type, extends(probe_t) :: station_t(m)
integer, len :: m = 2
integer :: levels(m)
end type station_t
type(probe_t) :: p0 ! all defaults: probe_t(dp, 3)
type(probe_t(sp)) :: p1 ! wp positional, n defaulted
type(probe_t(n=5)) :: p2 ! keyword skips wp
type(probe_t(sp, 4)) :: p3
type(station_t(sp, 4, 6)) :: s ! wp, n (parent), then m
type(station_t(m=1)) :: s2
print '(a, 2(i0, 1x))', 'p0 (wp,n): ', p0%wp, p0%n
print '(a, 2(i0, 1x))', 'p1 (wp,n): ', p1%wp, p1%n
print '(a, 2(i0, 1x))', 'p2 (wp,n): ', p2%wp, p2%n
print '(a, 3(i0, 1x))', 's (wp,n,m): ', s%wp, s%n, s%m
print '(a, 3(i0, 1x))', 's2 (wp,n,m): ', s2%wp, s2%n, s2%m
print '(a, 2(i0, 1x))', 'parent part of s: ', s%probe_t%wp, s%probe_t%n
! Structure constructors: the type parameters come first, then the component values.
p3 = probe_t(sp, 4)(reading=[1.0_sp, 2.0_sp, 3.0_sp, 4.0_sp], tag='ABCD')
print '(a, 4f5.1, 2a)', 'p3%reading: ', p3%reading, ' tag=', p3%tag
s%probe_t = p3 ! the parent component is an ordinary component
s%levels = [1, 2, 3, 4, 5, 6]
print '(a, 6i3)', 's%levels: ', s%levels
print '(a, 4f5.1)', 's%reading (inherited): ', s%reading
end program pdt_order
Output:
p0 (wp,n): 8 3
p1 (wp,n): 4 3
p2 (wp,n): 8 5
s (wp,n,m): 4 4 6
s2 (wp,n,m): 8 3 1
parent part of s: 4 4
p3%reading: 1.0 2.0 3.0 4.0 tag=ABCD
s%levels: 1 2 3 4 5 6
s%reading (inherited): 1.0 2.0 3.0 4.0
Source: J3/23-007r1: 7.5.3.2 Type parameter order, 7.5.7 Type extension, 7.5.9 Derived-type specifier
GNU Fortran 16.2 runs it as shown. The extended type inherits the parent’s parameters (they are s%wp and s%n, and
also s%probe_t%wp), and m=1 leaves wp and n at their defaults. Flang 23 stops in code generation, as before.
The compilers diagnose a wrong derived-type specifier at compile time. Each of the following is a separate file that
uses the module field_mod with field_t(wp, nx, ny) from the first example (defaults only on wp), and each is
rejected by both compilers, with different words:
program bad_missing
use field_mod
implicit none
type(field_t(dp, 4)) :: f ! ny has no default and no value
end program bad_missing
GNU Fortran 16.2:
bad_missing.f90:4:23:
4 | type(field_t(dp, 4)) :: f ! ny has no default and no value
| 1
Error: The type parameter spec list at (1) does not contain enough parameter expressions
LLVM Flang 23:
error: Semantic errors in bad_missing.f90
bad_missing.f90:4:8: error: Type parameter 'ny' lacks a value and has no default
type(field_t(dp, 4)) :: f ! ny has no default and no value
^^^^^^^
The other mistakes, each in its own file with the same four-line frame (program, use field_mod, implicit none,
the declaration, end program), give these messages:
| Declaration | GNU Fortran 16.2 | LLVM Flang 23 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Initializing components that use the parameters
A component can have a default initialization (= expr, 7.5.4.6). The standard puts a restriction on it that matters
for PDTs: every type parameter and every array bound of an initialized component must be a colon or a constant
expression (C767). A component sized by a length parameter, such as u(nx, ny), therefore cannot have a default
initializer, and neither can a character component with len=n. A component whose only parameter is a kind parameter
can, because a kind parameter is a constant.
Two further limits come from how the kind parameter can be used in a literal. In 1.5_wp the text after the
underscore is a kind-param, which is a digit string or a named constant (R709, C718), and a kind type parameter
is neither. GNU Fortran follows the text and rejects it; LLVM Flang accepts it. Writing the conversion explicitly, as
real(1.5, wp) or real(0, wp), is valid everywhere and is what the examples on this page do.
program kind_literal
implicit none
type :: pt(wp)
integer, kind :: wp = 8
real(wp) :: x = 1.5_wp
end type pt
type(pt) :: a
print *, a%x
end program kind_literal
GNU Fortran 16.2:
13_kind_literal.f90:5:26:
5 | real(wp) :: x = 1.5_wp
| 1
Error: Missing kind-parameter at (1)
13_kind_literal.f90:8:14:
8 | print *, a%x
| 1
Error: 'x' at (1) is not a member of the 'PDTpt_8' structure
LLVM Flang 23 accepts the program and prints the value 1.5. Flang’s acceptance is a processor extension here: the constraint text is unambiguous that the kind parameter must be a named constant.
The default initialization of a component sized by length parameters is rejected, correctly, by GNU Fortran at the definition, and by Flang only when a type with run-time length values is instantiated:
program init_default
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
type :: field_t(wp, nx, ny)
integer, kind :: wp = dp
integer, len :: nx, ny
real(wp) :: u(nx, ny) = real(0, wp)
end type field_t
type(field_t(dp, 3, 2)) :: a
print *, a%u
end program init_default
GNU Fortran 16.2 (the first of several lines it prints for the same declaration):
14_default_init_len.f90:8:27:
8 | real(wp) :: u(nx, ny) = real(0, wp)
| 1
Error: The component 'u' at (1) of derived type 'field_t' has paramterized type or array length parameters, which is not compatible with a default initializer
(The misspelling "paramterized" is in the compiler.) LLVM Flang 23 passes this exact program through its semantic
checks and stops in code generation; with a run-time value, type(field_t(dp, n, n))::a for an integer variable n, it reports:
error: Semantic errors in 14b_default_init_runtime.f90
14b_default_init_runtime.f90:8:17: error: Shape of initialized object 'u' must be constant
real(wp) :: u(nx, ny) = real(0, wp)
^
14b_default_init_runtime.f90:13:5: in the context: instantiation of parameterized derived type 'field_t(wp=8_4,nx=n,ny=n)'
type(field_t(dp, n, n)) :: a
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The way to give such a component a value is an assignment after the declaration, as the examples do (a%u = 0), or an
explicit initialization of the whole object in a type declaration statement (the p of the structure constructor
example below).
GNU Fortran 16.2 has one more defect on this path. It does not apply the implicit conversion of an initializer to a
kind parameter: real(wp)::x(3) = 0 is rejected with Error: Cannot convert INTEGER(4) to REAL(0) at (1)
(15_init_conversion.f90), while Flang accepts it and prints three zeros. Writing real(0, wp) avoids it.
Type parameter inquiry
The value of a type parameter of an object is obtained with object%name (R916, 9.4.5). The syntax looks like a
component reference but is not one: it is not a variable, so it can never be assigned to, and it is used only as a
primary in an expression. The name must be a parameter of the declared type of the object (C923). It works for the
intrinsic types too, where it gives the same value as the functions kind and len: x%kind, s%len. A deferred
parameter of an unallocated allocatable or a disassociated pointer shall not be inquired about (9.4.5).
The next program shows the inquiries, an array of a parameterized type, and a function whose result type is a parameterized type with length parameters computed from its dummy argument. A function result with length parameters that are specification expressions of the dummy arguments is the PDT counterpart of an automatic array result, and needs no allocation.
module field_mod
use, intrinsic :: iso_fortran_env, only: real32, real64
implicit none
private
public :: sp, dp, field_t, label_t, laplacian
integer, parameter :: sp = real32
integer, parameter :: dp = real64
type :: field_t(wp, nx, ny)
integer, kind :: wp = dp
integer, len :: nx, ny
real(wp) :: u(nx, ny)
end type field_t
! A length parameter can size a character component as well.
type :: label_t(n)
integer, len :: n
character(len=n) :: text
end type label_t
contains
! The result's length parameters are specification expressions of the dummy arguments.
function laplacian(f) result(r)
type(field_t(dp, *, *)), intent(in) :: f
type(field_t(dp, f%nx - 2, f%ny - 2)) :: r
integer :: i, j
do j = 2, f%ny - 1
do i = 2, f%nx - 1
r%u(i - 1, j - 1) = f%u(i - 1, j) + f%u(i + 1, j) + f%u(i, j - 1) + f%u(i, j + 1) - 4.0_dp * f%u(i, j)
end do
end do
end function laplacian
end module field_mod
program pdt_inquiry
use field_mod
implicit none
type(field_t(dp, 5, 4)) :: f
type(label_t(8)) :: lab
type(field_t(sp, 2, 2)) :: tiny(3) ! an array of a parameterized type: all elements share the parameters
type(field_t(dp, :, :)), allocatable :: g
real(sp) :: x
character(len=12) :: word
complex(dp) :: z
integer :: i, j
! Type parameter inquiry also works for the intrinsic types.
print '(a, i0, a, i0, a, i0, a, i0)', 'x%kind=', x%kind, ' word%len=', word%len, &
' z%kind=', z%kind, ' lab%n=', lab%n
do j = 1, f%ny
do i = 1, f%nx
f%u(i, j) = real(i * i + j * j, dp)
end do
end do
g = laplacian(f) ! g gets the result's parameters
print '(a, 2(i0, 1x), a, f5.1)', 'laplacian: nx, ny = ', g%nx, g%ny, ' value ', g%u(1, 1)
! An array element has the parameters of the array.
print '(a, i0, a, i0)', 'tiny(2)%nx = ', tiny(2)%nx, ' tiny(3)%ny = ', tiny(3)%ny
lab%text = 'heatflow-long' ! truncated to len=8
print '(3a)', '[', lab%text, ']'
end program pdt_inquiry
Output:
x%kind=4 word%len=12 z%kind=8 lab%n=8
laplacian: nx, ny = 3 2 value 4.0
tiny(2)%nx = 2 tiny(3)%ny = 2
[heatflow]
The values printed for x, word and z are inquiries only, so the undefined variables are not read. The Laplacian of
i*i + j*j at the interior point (2, 2) is 4, which is the printed value. Two things are worth noting. A character
component whose length is a type parameter behaves like any character variable: the text is truncated to the length
and padded with blanks on assignment, and lab%text shows heatflow for the 13-character 'heatflow-long'. And the
result type field_t(dp, f%nx - 2, f%ny - 2) is legal because f%nx is a type parameter inquiry on a dummy argument,
which is permitted in a specification expression (10.1.11).
Flang 23 cannot build this example (length parameters). GNU Fortran 16.2 builds it, but the standard says the inquiry is scalar for an array designator (the note to 9.4.5: "It is scalar even if designator is an array") and GNU Fortran 16.2 does not follow that for a derived-type parameter:
program inquiry_on_arrays
implicit none
type :: field_t(nx)
integer, len :: nx
real :: u(nx)
end type field_t
type(field_t(2)) :: b(3)
real :: r(4)
character(len=5) :: s(3)
! A type parameter inquiry is scalar even when the designator is an array (9.4.5).
print '(a, i0, a, i0, a, i0)', 'rank(r%kind)=', rank(r%kind), ' rank(s%len)=', rank(s%len), &
' rank(b%nx)=', rank(b%nx)
end program inquiry_on_arrays
Output on GNU Fortran 16.2:
rank(r%kind)=0 rank(s%len)=0 rank(b%nx)=1
The correct answer for the last one is 0. The inquiry on an array of a PDT is array-valued in this compiler. Inquire on
an element, b(1)%nx, when the program has to run on it.
Provenance: parameterized derived types are a Fortran 2003 feature (the Fortran 2003 compatibility list in 4.3.5 of J3/23-007r1 refers to the Fortran 2003 rules for sequence types with parameters and for statement functions of parameterized type). The type parameter inquiry of 9.4.5 applies to intrinsic and derived types alike.
Structure constructors
A structure constructor is the derived-type specifier followed by the component values: probe_t(sp, 4)(reading=..., tag=...)
(R756, 7.5.10). The first parenthesised list carries the type parameters, the second the components. The component
rules are those of an ordinary type: values in component order or with keywords, all components without a default
initialization needing a value (C7104), a component keyword, once used, required on every later value (C7105). In addition, the
declared type and type parameters of a nonpointer component and of the corresponding expression "shall conform in the
same way as for a variable and expr in an intrinsic assignment statement" (7.5.10), so the shape of an array
expression must match what the length parameters give. The p3 assignment in the type parameter order example is a
structure constructor, and the next program also uses one to initialize a variable and to define a named constant of a
parameterized type:
program io_and_constants
implicit none
type :: pair_t(n)
integer, len :: n
integer :: v(n)
character(len=n) :: tag
end type pair_t
type(pair_t(2)) :: p = pair_t(2)([10, 20], 'ab') ! initialization expression
type(pair_t(3)), parameter :: q = pair_t(3)([1, 2, 3], 'xyz') ! a named constant of a PDT
print *, p ! list-directed output: components only
write (*, '(3i4, 1x, a)') q%v, q%tag
print '(a, i0, a, i0)', 'p%n=', p%n, ' q%n=', q%n
end program io_and_constants
Output:
10 20 ab
1 2 3 xyz
p%n=2 q%n=3
This works on GNU Fortran 16.2 (Flang 23 stops at the length parameter). The type parameters are not part of the values transferred by input/output: list-directed output prints the components in order and nothing else. A value that depends on a length parameter can still be a named constant, because the constant’s own parameters are constants. The input/output statements in general are in Input/Output: Units and Files.
A structure constructor for an extended parameterized type is broken in GNU Fortran 16.2. The type below extends a
type with a length parameter, and the constructor is correct (the keywords a and b name distinct components):
program ext_constructor
implicit none
type :: base_t(n)
integer, len :: n
real :: a(n)
end type base_t
type, extends(base_t) :: derived_t(m)
integer, len :: m
integer :: b(m)
end type derived_t
type(derived_t(2, 3)) :: d
d = derived_t(2, 3)(a=[1.0, 2.0], b=[1, 2, 3])
print *, d%a, d%b
end program ext_constructor
GNU Fortran 16.2 rejects it:
02b_extended_constructor.f90:13:38:
13 | d = derived_t(2, 3)(a=[1.0, 2.0], b=[1, 2, 3])
| 1
Error: Component 'b' is initialized twice in the structure constructor at (1)
The same false error appeared for every extended parameterized type tried, with only a kind parameter, with only length
parameters and with both, and always names the last component of the extension. The workaround on this compiler is to
assign the components one by one, as the type parameter order example does with s%probe_t = p3 and s%levels = ....
Flang 23 does not reach the point for the length-parameter version.
Assumed and deferred length parameters
A length parameter of an object can be left to be determined by something else, in two ways that mirror
character(len=*) and character(len=:):
-
Assumed,
*(7.2): used for a dummy argument, an associate name in aselect typeconstruct, or a named constant of character type. The object takes the value from the actual argument or selector. Only a length parameter can be assumed. -
Deferred,
:(7.2, C702): for an entity with theallocatableorpointerattribute only. The value is given when the object is allocated, by a type-spec inallocate, bysource=ormold=, by intrinsic assignment to an allocatable variable (which reallocates it, 10.2.1.3), by pointer assignment, or by argument association.
module field_mod
use, intrinsic :: iso_fortran_env, only: real32, real64
implicit none
private
public :: sp, dp, field_t, mean_temperature, make_field
integer, parameter :: sp = real32
integer, parameter :: dp = real64
type :: field_t(wp, nx, ny)
integer, kind :: wp = dp
integer, len :: nx, ny
real(wp) :: u(nx, ny)
end type field_t
! The kind parameter takes part in generic resolution, the length parameters do not.
interface mean_temperature
module procedure mean_sp, mean_dp
end interface mean_temperature
contains
! Assumed length parameters: nx and ny are taken from the actual argument.
function mean_dp(f) result(m)
type(field_t(dp, *, *)), intent(in) :: f
real(dp) :: m
m = sum(f%u) / real(f%nx * f%ny, dp)
end function mean_dp
function mean_sp(f) result(m)
type(field_t(sp, *, *)), intent(in) :: f
real(sp) :: m
m = sum(f%u) / real(f%nx * f%ny, sp)
end function mean_sp
! A function result with deferred length parameters: the allocation inside decides the size.
function make_field(nx, ny, t0) result(f)
integer, intent(in) :: nx, ny
real(dp), intent(in) :: t0
type(field_t(dp, :, :)), allocatable :: f
allocate (field_t(dp, nx, ny) :: f) ! type-spec in allocate sets the deferred parameters
f%u = t0
end function make_field
end module field_mod
program pdt_assumed_deferred
use field_mod
implicit none
type(field_t(dp, 3, 2)) :: a
type(field_t(sp, 4, 4)) :: b
type(field_t(dp, :, :)), allocatable :: c, d
type(field_t(dp, :, :)), pointer :: p
a%u = reshape([1, 2, 3, 4, 5, 6], [3, 2])
b%u = 2.5_sp
print '(a, f6.2)', 'mean(a) = ', mean_temperature(a)
print '(a, f6.2)', 'mean(b) = ', mean_temperature(b)
c = make_field(5, 4, 10.0_dp) ! assignment allocates c with the result's parameters
print '(a, 2(i0, 1x), f6.1)', 'c%nx, c%ny, mean: ', c%nx, c%ny, mean_temperature(c)
allocate (d, source=a) ! source= supplies the type parameters
print '(a, 2(i0, 1x), f4.1)', 'd%nx, d%ny, max difference: ', d%nx, d%ny, maxval(abs(d%u - a%u))
allocate (field_t(dp, 2, 2) :: p) ! a pointer with deferred parameters
p%u = 7.0_dp
print '(a, 2(i0, 1x), f6.1)', 'p%nx, p%ny, mean: ', p%nx, p%ny, mean_temperature(p)
deallocate (p)
! Reallocation on assignment changes the length parameters of a deferred-parameter variable.
c = a
print '(a, 2(i0, 1x))', 'after c = a: c%nx, c%ny: ', c%nx, c%ny
end program pdt_assumed_deferred
Output on GNU Fortran 16.2:
mean(a) = 3.50
mean(b) = 2.50
c%nx, c%ny, mean: 5 4 10.0
d%nx, d%ny, max difference: 3 2 0.0
p%nx, p%ny, mean: 2 2 7.0
after c = a: c%nx, c%ny: 3 2
The points the example shows, with the rules behind them:
-
mean_dpandmean_spreceive any extents because they declare*for both length parameters, and the actual argument carries its own values, which the body reads back asf%nxandf%ny. The kind parameter is not assumed: it is written in the dummy’s type, sotype(field_t(dp, *, *))accepts only double-precision fields, and the kind values of the actual and the dummy must agree (15.5.2.5). -
A function result with deferred parameters, like a result that is an allocatable
character(len=:), is allocated inside the function. The caller’sc = make_field(5, 4, 10.0_dp)allocatescwith the parameters of the result, becausechas deferred parameters (10.2.1.3). -
allocate (d, source=a)takes the parameters from the source expression. With a type-spec,allocate (field_t(dp, 2, 2)::p), the parameters are those named there. In a type-spec, the value of a kind parameter must still be a constant expression, and an asterisk is allowed only for a dummy argument that is being allocated (C7100). -
Intrinsic assignment to an allocatable variable with deferred parameters reallocates it when the parameter values differ (10.2.1.3), and the last two lines show it:
cchanges from5 x 4to3 x 2without anydeallocate. A variable with nondeferred parameters does not reallocate: the standard requires its parameters to equal those of the expression (10.2.1.2, case (10)).
If an allocate gives parameters that disagree with a nondeferred parameter of the object, the standard says an error
condition occurs (9.7.1.2). Flang diagnoses the obvious cases at compile time; GNU Fortran 16.2 does not diagnose it at
all, neither at compile time nor at run time, even when a stat= is present:
program allocate_mismatch
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
type :: field_t(wp, nx, ny)
integer, kind :: wp = dp
integer, len :: nx, ny
real(wp) :: u(nx, ny)
end type field_t
type(field_t(dp, 4, 4)), allocatable :: x ! nondeferred: always 4 x 4
type(field_t(dp, 3, 2)) :: a
integer :: stat
a%u = 1.0_dp
allocate (x, source=a, stat=stat) ! parameters of source differ from those of x
print '(a, i0)', 'stat = ', stat
print '(a, l1)', 'allocated(x) = ', allocated(x)
end program allocate_mismatch
GNU Fortran 16.2 builds and runs it, allocates a 4 by 4 field from a 3 by 2 source and reports success:
stat = 0
allocated(x) = T
LLVM Flang 23 rejects it at compile time:
error: Semantic errors in 09_allocate_mismatch.f90
09_allocate_mismatch.f90:16:13: error: Derived type parameters of allocatable object must be the same as the corresponding ones of SOURCE or MOLD expression
allocate (x, source=a, stat=stat) ! parameters of source differ from those of x
^
A program that is not correct under the standard is not obliged to be diagnosed, but one that runs "successfully" with a
wrong-sized object is a bad failure mode; keep nondeferred allocatable objects out of PDT code, and use deferred
parameters (:) so that the allocation defines them.
Assignment, argument association and interfaces
Intrinsic assignment between objects of a parameterized type requires the same type, equal kind parameters, and equal length parameters, with one exception: an allocatable variable whose corresponding length parameter is deferred is reallocated (10.2.1.2, list item (10)). A structure assigned from one with other parameters is an error, and compilers diagnose it when the values are constants:
program bad_assign_len
use field_mod
implicit none
type(field_t(dp, 3, 2)) :: a
type(field_t(dp, 2, 3)) :: b
a%u = 1
b = a ! length parameters differ
end program bad_assign_len
GNU Fortran 16.2:
bad_assign_len.f90:7:3-6:
7 | b = a ! length parameters differ
| 1~ 2
Error: Mismatched type parameters 'nx'(2/3) in assignment at (1)/(2)
bad_assign_len.f90:7:3-6:
7 | b = a ! length parameters differ
| 1~ 2
Error: Mismatched type parameters 'ny'(3/2) in assignment at (1)/(2)
LLVM Flang 23:
error: Semantic errors in bad_assign_len.f90
bad_assign_len.f90:7:3: error: No intrinsic or user-defined ASSIGNMENT(=) matches operand types TYPE(field_t(nx=2_4,ny=3_4,wp=8_4)) and TYPE(field_t(nx=3_4,ny=2_4,wp=8_4))
b = a ! length parameters differ
^^^^^
The same program with type(field_t(sp, 3, 2))::b fails in the same way, naming the kind parameter (GNU Fortran:
Error: Mismatched type parameters 'wp'(4/8) in assignment at (1)/(2); Flang: the same "No intrinsic or user-defined
ASSIGNMENT(=)" message with wp=4_4 and wp=8_4). The remedy is a defined assignment, which converts between
precisions explicitly (see
Interfaces, Generics and Operator Overloading),
or copying the components.
When the length parameters are run-time values, the compiler cannot see the mismatch, and the program is not
conforming. GNU Fortran 16.2 does not check it at run time even with -fcheck=all:
module field_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
type :: field_t(wp, nx, ny)
integer, kind :: wp = dp
integer, len :: nx, ny
real(wp) :: u(nx, ny)
end type field_t
end module field_mod
program runtime_len
use field_mod
implicit none
integer :: n, m
n = 3
m = 2
call run(n, m)
contains
subroutine run(n, m)
integer, intent(in) :: n, m
type(field_t(dp, n, m)) :: a
type(field_t(dp, m, n)) :: b
a%u = reshape([1, 2, 3, 4, 5, 6], [n, m])
b = a
print '(a, 6f4.0)', 'b%u = ', b%u
end subroutine run
end program runtime_len
Output on GNU Fortran 16.2:
b%u = 1. 2. 3. 4. 5. 6.
The 3 x 2 field was copied into a 2 x 3 one as a flat sequence, with no complaint; the layout of b has
silently been reinterpreted. Treat an equal-length requirement as something the program must guarantee itself, for
example with an explicit all([a%nx, a%ny] == [b%nx, b%ny]) test or, better, by passing intent(in) assumed-parameter
arguments so only one value exists.
Argument association. For a nonallocatable nonpointer dummy, the kind parameters of the actual argument must agree with those of the dummy, and the length
parameters of a present actual argument must agree with those of the dummy that are not assumed (15.5.2.5). A dummy
with * accepts any value, a dummy with explicit values checks them at the call (not necessarily at compile time),
and a dummy with deferred parameters (allocatable or pointer) requires an actual argument that has the same deferred
parameters (15.5.2.6).
Interfaces. A procedure that has a dummy argument of parameterized derived type needs an explicit interface at every call (15.4.2.2, the list of cases in which an explicit interface is required includes "is of a parameterized derived type"). A module procedure or an internal procedure always has one, which is one more reason to keep every procedure of this page in a module. This rule was not exercised here, because the single-file programs have no external procedures.
Generic resolution
The kind parameter takes part in generic resolution and the length parameter does not (7.2, 15.5.5.2). A generic such as
mean_temperature in the assumed/deferred example is resolved at compile time on sp versus dp. Two specifics that
differ only in a length value cannot be told apart, because the same type with other lengths is the same type for
resolution:
module ambiguous_mod
use, intrinsic :: iso_fortran_env, only: real64
implicit none
integer, parameter :: dp = real64
type :: vec_t(n)
integer, len :: n
real(dp) :: x(n)
end type vec_t
interface norm
module procedure norm3, norm4
end interface norm
contains
function norm3(v) result(r)
type(vec_t(3)), intent(in) :: v
real(dp) :: r
r = sqrt(sum(v%x**2))
end function norm3
function norm4(v) result(r)
type(vec_t(4)), intent(in) :: v
real(dp) :: r
r = sqrt(sum(v%x**2))
end function norm4
end module ambiguous_mod
program generic_by_length
use ambiguous_mod
implicit none
type(vec_t(3)) :: a
a%x = [3.0_dp, 4.0_dp, 0.0_dp]
print *, norm(a)
end program generic_by_length
GNU Fortran 16.2 rejects the generic at its definition:
08_generic_by_length_error.f90:17:2:
17 | function norm3(v) result(r)
| 1
......
23 | function norm4(v) result(r)
| 2
Error: Ambiguous interfaces in generic interface 'norm' for 'norm3' at (1) and 'norm4' at (2)
Flang 23 reports no semantic error for the ambiguous pair before it stops in code generation on the length parameter, so
the check was not observed there. To dispatch on a size, write a single procedure with an assumed length parameter,
type(vec_t(*)), and use v%n inside (the procedure then handles both). To dispatch on the precision, use the kind
parameter. The rules for distinguishing specific procedures are in
Interfaces, Generics and Operator Overloading.
Type extension, bindings and polymorphism
A parameterized type can be extended (7.5.7). The extension inherits the parent’s parameters, which come first in the
type parameter order (7.5.3.2) and can be named in its derived-type specifier, and it may add its own. The parent
component is an ordinary component named after the parent, as in s%probe_t in the type parameter order example.
A type-bound procedure can be bound to a parameterized type, and several rules constrain how:
-
The passed-object dummy argument shall have the same declared type as the type being defined, and all of its length type parameters shall be assumed (C765). A binding therefore declares
class(series_t(*)), neverclass(series_t(3)), so that it works for every length. Kind parameters, on the other hand, are written out. -
A final subroutine has a single dummy of the type with all length parameters assumed (C791), and two final subroutines of one type shall not have the same kind parameters and rank (C793).
-
A
type isorclass isguard ofselect typeshall specify each length parameter as assumed,type is (field_t(dp, *, *))(C1165), so the guard cannot distinguish values of a length parameter, but it does distinguish kind parameters. -
A defined input/output procedure’s
dtvargument shall have all length parameters assumed (C1237), see Derived-Type and Asynchronous I/O.
The following program is a sensor hierarchy with only a kind parameter, a type that both compilers handle completely: the
calibrated sensor extends the sensor, overrides a binding, and is reached through a polymorphic pointer with select type.
module sensor_mod
use, intrinsic :: iso_fortran_env, only: real32, real64
implicit none
private
public :: sp, dp, sensor_t, calibrated_sensor_t
integer, parameter :: sp = real32
integer, parameter :: dp = real64
! A probe reading whose precision is a kind parameter.
type :: sensor_t(wp)
integer, kind :: wp = dp
real(wp) :: raw = real(0, wp)
contains
procedure :: value => sensor_value
procedure :: describe => sensor_describe
end type sensor_t
type, extends(sensor_t) :: calibrated_sensor_t
real(wp) :: offset = real(0, wp) ! the parent's kind parameter is visible here
contains
procedure :: value => calibrated_value ! overrides the binding
end type calibrated_sensor_t
contains
function sensor_value(self) result(r)
class(sensor_t(dp)), intent(in) :: self
real(dp) :: r
r = self%raw
end function sensor_value
function calibrated_value(self) result(r)
class(calibrated_sensor_t(dp)), intent(in) :: self
real(dp) :: r
r = self%raw + self%offset
end function calibrated_value
subroutine sensor_describe(self)
class(sensor_t(dp)), intent(in) :: self
print '(a, f7.2)', 'sensor value ', self%value()
end subroutine sensor_describe
end module sensor_mod
program pdt_kind_hierarchy
use sensor_mod
implicit none
type(sensor_t(dp)), target :: s
type(calibrated_sensor_t(dp)), target :: c
class(sensor_t(dp)), pointer :: item
integer :: i
s = sensor_t(dp)(raw=20.0_dp)
c%raw = 20.0_dp
c%offset = -1.5_dp
do i = 1, 2
if (i == 1) item => s
if (i == 2) item => c
select type (x => item)
type is (sensor_t(dp))
print '(a, i0)', 'type is sensor_t(dp), wp = ', x%wp
class is (calibrated_sensor_t(dp))
print '(a, i0, a, f5.1)', 'class is calibrated_sensor_t(dp), wp = ', x%wp, ', offset = ', x%offset
end select
call item%describe() ! dispatch on the dynamic type
end do
end program pdt_kind_hierarchy
Output, identical on GNU Fortran 16.2 and LLVM Flang 23:
type is sensor_t(dp), wp = 8
sensor value 20.00
class is calibrated_sensor_t(dp), wp = 8, offset = -1.5
sensor value 18.50
Three details follow from the rules above. describe is inherited from sensor_t by calibrated_sensor_t and calls
self%value(), which dispatches to the override, hence the corrected 18.50 for the second item. The constructor
sensor_t(dp)(raw=20.0_dp) is a structure constructor of a non-extended type and works; the equivalent constructor
for calibrated_sensor_t is the broken case of the previous section, so the example sets c%raw and c%offset. And
the defaults are written real(0, wp) for the reason given in
Initializing components that use the parameters.
When the length parameters are in the hierarchy, the binding has to use the assumed form of C765. The attempt
to declare the extension’s override with a fixed length (class(w_t(3))) is diagnosed by both compilers (the
program is 04c_passed_object_length.f90, the one below with the dummy of w_describe changed from class(w_t(*)) to
class(w_t(3))):
04c_passed_object_length.f90:12:13:
12 | procedure :: describe => w_describe
| 1
Error: All LEN type parameters of the passed dummy argument 'self' of 'w_describe' at (1) must be ASSUMED.
LLVM Flang 23: error: Passed-object dummy argument 'self' of procedure 'describe' has non-assumed length parameter 'n'.
The correct form, with class(w_t(*)), is where GNU Fortran 16.2 fails:
module series_mod
implicit none
type :: s_t(n)
integer, len :: n
real :: t(n)
contains
procedure :: describe => s_describe
end type s_t
type, extends(s_t) :: w_t
integer :: first = 1
contains
procedure :: describe => w_describe
end type w_t
contains
subroutine s_describe(self)
class(s_t(*)), intent(in) :: self
print *, 'series', self%n
end subroutine s_describe
subroutine w_describe(self)
class(w_t(*)), intent(in) :: self
print *, 'window', self%n, self%first
end subroutine w_describe
end module series_mod
program len_hierarchy
use series_mod
implicit none
type(w_t(3)) :: w
type(s_t(2)) :: s
call s%describe()
call w%describe()
end program len_hierarchy
This is a conforming program. GNU Fortran 16.2 stops with an internal compiler error, with or without optimization and
with and without optimization (-O2) and with and without the strict options used on this page:
04b_extended_len_ice.f90:31:25:
31 | end program len_hierarchy
| 1
internal compiler error: in gfc_get_symbol_decl, at fortran/trans-decl.cc:1774
(a backtrace of the compiler follows). The same internal error appeared for each of these, all with a type that extends
a parameterized type and has length parameters: a dummy declared class(w_t(*)), type(w_t(*)) or with a keyword
class(w_t(n=*)), and a class is (w_t(*)) guard of select type. A dummy class(s_t(*)) of the parent type
accepts an actual argument of the extension without problems, so a hierarchy whose procedures all take the base class
and whose extension adds only data can be written; overriding is what cannot. Flang 23 does not reach this, because
it stops at the length parameter first.
The polymorphic allocatable form has a defect with kind parameters too: a polymorphic allocatable variable of a
parameterized type that has an allocatable component crashes at the allocate statement. The type is the simplest one,
a kind parameter and an allocatable array:
program polymorphic_pdt
implicit none
integer, parameter :: dp = kind(1.0d0)
type :: series_t(wp)
integer, kind :: wp = dp
real(wp), allocatable :: t(:)
end type series_t
class(series_t(dp)), allocatable :: item
allocate (series_t(dp) :: item)
print '(a)', 'allocated'
item%t = [1.0_dp, 2.0_dp]
print '(a, i0)', 'size = ', size(item%t)
end program polymorphic_pdt
GNU Fortran 16.2 builds it and the program dies before printing anything:
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
Backtrace for this error:
The same variable declared without the allocatable component (a scalar real(wp)::raw, as in the sensor example),
or the same type used as a nonpolymorphic variable, works. Flang 23 prints allocated and size = 2. The same crash
was seen with allocate (item, source=...) and with a type extension that carries the allocatable component, so a polymorphic
variable of a PDT should have scalar or fixed-size components on GNU Fortran 16.2.
Finally, the deferred-parameter transfer move_alloc has a defect:
program move_alloc_pdt
implicit none
type :: field_t(nx)
integer, len :: nx
real :: u(nx)
end type field_t
type(field_t(:)), allocatable :: g, h
allocate (field_t(4) :: g)
g%u = 1.0
call move_alloc(g, h)
print '(a, l1, a, i0)', 'g allocated: ', allocated(g), ' h%nx: ', h%nx
end program move_alloc_pdt
GNU Fortran 16.2 prints the correct values and then dies when the program ends:
g allocated: F h%nx: 4
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
Backtrace for this error:
Replace move_alloc with an assignment, h = g followed by deallocate (g), in code that has to run on this
compiler. The assignment (reallocation on assignment) was shown above and works.
Components of parameterized type
A parameterized type can have components of another parameterized type, and the outer type passes its own parameters on. This is how one models a double-buffered field, which Jacobi sweeps use: two arrays of identical size and kind in one object.
module sim_mod
use, intrinsic :: iso_fortran_env, only: real32, real64
implicit none
private
public :: sp, dp, field_t, sim_t, sweep
integer, parameter :: sp = real32
integer, parameter :: dp = real64
type :: field_t(wp, nx, ny)
integer, kind :: wp = dp
integer, len :: nx, ny
real(wp) :: u(nx, ny)
end type field_t
! The parameters of the enclosing type are used as the parameters of its components.
type :: sim_t(wp, nx, ny)
integer, kind :: wp = dp
integer, len :: nx, ny
type(field_t(wp, nx, ny)) :: now, next
integer :: steps = 0
end type sim_t
contains
subroutine sweep(s, residual)
type(sim_t(dp, *, *)), intent(inout) :: s
real(dp), intent(out) :: residual
integer :: i, j
s%next%u = s%now%u ! keep the boundary values
do j = 2, s%ny - 1
do i = 2, s%nx - 1
s%next%u(i, j) = 0.25_dp * (s%now%u(i - 1, j) + s%now%u(i + 1, j) + s%now%u(i, j - 1) + s%now%u(i, j + 1))
end do
end do
residual = maxval(abs(s%next%u - s%now%u))
s%now%u = s%next%u
s%steps = s%steps + 1
end subroutine sweep
end module sim_mod
program pdt_nested
use sim_mod
implicit none
integer :: n
real(dp) :: res
n = 21
block
type(sim_t(dp, n, n)) :: s ! sizes taken at run time, stored on the stack
s%now%u = 0.0_dp
s%now%u(1, :) = 100.0_dp ! hot edge
res = 1.0_dp
do while (res > 1.0e-3_dp .and. s%steps < 5000)
call sweep(s, res)
end do
print '(a, i0, a, es9.2)', 'steps = ', s%steps, ' residual = ', res
print '(a, f7.3)', 'centre temperature = ', s%now%u(11, 11)
print '(a, 2(i0, 1x))', 'now%nx, next%ny = ', s%now%nx, s%next%ny
end block
end program pdt_nested
Output on GNU Fortran 16.2:
steps = 502 residual = 9.96E-04
centre temperature = 24.920
now%nx, next%ny = 21 21
The component declarations type(field_t(wp, nx, ny))::now, next use the outer type’s parameters as a
type-param-value, which C759 allows because they are specification expressions of the enclosing definition. The
procedure sweep takes the whole object with assumed lengths. The relaxation is the Jacobi method of the heatflow
solver on a 21 by 21 plate with a hot first row: 502 sweeps bring the largest change below 1e-3, and the plate
centre settles at about 24.9, a quarter of the hot edge, as symmetry gives. Flang 23 cannot build it.
The same type with wp only (and the extents as ordinary components and an allocatable array) is the heatflow grid_t
pattern, which has no such limits; see the next section.
Where parameterized types cannot be used
| Context | Rule | Clause |
|---|---|---|
Types with |
a derived type with the BIND attribute shall not have type parameters (C1802); a C structure is a fixed layout, so pass the arrays and extents separately, as heatflow’s |
18.3.4 |
Sequence types |
Fortran 2003 permitted a |
4.3.5 |
Statement functions |
a statement function shall not be of parameterized derived type (C1591); Fortran 2003 permitted it |
15.6.4 |
|
the length parameters shall be assumed (C1165, C791, C765, C1237) |
11.1.11, 7.5.6.1, 7.5.4.5, 12.6.4.8.2 |
Kind parameter values |
constant expressions only; a run-time |
7.2 |
Default initialization |
no component whose type parameters or bounds are not constants (C767) |
7.5.4.6 |
|
an actual argument of a type with type parameters, type-bound procedures or final subroutines cannot be associated with an assumed-type dummy |
15.5.2.5 |
Procedures with an implicit interface |
a procedure with a dummy argument of parameterized derived type needs an explicit interface (15.4.2.2) |
15.4.2.2 |
Compiler support and practical limits
PDT behavior depends more on the compiler than any other feature on these pages. What was observed on 2026-10-19, with the file that reproduces each item named in parentheses (in the scratch directory used to check this page, not committed):
| Item | GNU Fortran 16.2 | LLVM Flang 23 |
|---|---|---|
Kind parameters only ( |
complete in everything tried, except the polymorphic crash below |
complete; the PDT design note in the Flang documentation says kind-parameterized types are "already implemented" |
Length parameters |
accepted and executed in everything tried except the cases below |
semantic checks accept them, then stops with |
|
rejected, "Missing kind-parameter" (C718 requires a named constant) |
accepted |
|
rejected, "Cannot convert INTEGER(4) to REAL(0)" |
accepted |
Default initializer on a component sized by length parameters |
rejected (C767) |
rejected at instantiation with run-time values |
Structure constructor of an extended PDT |
false error "initialized twice" (every case tried) |
works for a kind-only extension; not reached for length parameters |
Assumed-length dummy of an extended PDT, |
internal compiler error |
not reached |
|
array-valued (must be scalar) |
not reached |
|
no diagnostic at all, object allocated with the wrong parameters |
compile-time error where visible |
Intrinsic assignment with run-time length mismatch |
no diagnostic, even with |
not reached |
Polymorphic allocatable with an allocatable component |
segmentation fault in |
works |
|
segmentation fault at program end |
not reached |
|
returns 576 bits for four different objects ( |
not reached |
The storage_size row is easy to reproduce: for type(field_t(5)) and type(field_t(2))::b(3) the intrinsic returned
576 for a, b(1) and b, while five default reals alone are 160 bits. Do not use storage_size, sizeof or transfer
on a length-parameterized object on this compiler.
The consequence is a practical rule, shown in the diagram. Use kind parameters freely; they are the reliable half. Use
length parameters only when the program is going to be built by GNU Fortran and a test suite covers the construct, and
prefer assumed (*) and deferred (:) lengths in the interfaces, which worked best.
The portable pattern combines a kind parameter with an allocatable component and uses the kind to drive generic resolution. It is the shape heatflow would take if its precision had to be a type parameter. Both compilers produce the same output for it:
module grid_mod
use, intrinsic :: iso_fortran_env, only: real32, real64
implicit none
private
public :: sp, dp, grid_t, make_grid, max_temperature
integer, parameter :: sp = real32
integer, parameter :: dp = real64
! The portable subset: one kind parameter, an allocatable component for the size.
type :: grid_t(wp)
integer, kind :: wp = dp
integer :: nx = 0, ny = 0
real(wp), allocatable :: u(:, :)
end type grid_t
interface make_grid
module procedure make_grid_sp, make_grid_dp
end interface make_grid
interface max_temperature
module procedure max_sp, max_dp
end interface max_temperature
contains
function make_grid_sp(nx, ny, t0) result(g)
integer, intent(in) :: nx, ny
real(sp), intent(in) :: t0 ! the kind of t0 selects this specific
type(grid_t(sp)) :: g
g%nx = nx
g%ny = ny
allocate (g%u(nx, ny), source=t0)
end function make_grid_sp
function make_grid_dp(nx, ny, t0) result(g)
integer, intent(in) :: nx, ny
real(dp), intent(in) :: t0
type(grid_t(dp)) :: g
g%nx = nx
g%ny = ny
allocate (g%u(nx, ny), source=t0)
end function make_grid_dp
pure function max_sp(g) result(m)
type(grid_t(sp)), intent(in) :: g
real(sp) :: m
m = maxval(g%u)
end function max_sp
pure function max_dp(g) result(m)
type(grid_t(dp)), intent(in) :: g
real(dp) :: m
m = maxval(g%u)
end function max_dp
end module grid_mod
program pdt_portable
use grid_mod
implicit none
type(grid_t(sp)) :: a
type(grid_t(dp)) :: b
a = make_grid(4, 3, 20.0_sp)
b = make_grid(6, 5, 20.0_dp)
a%u(2, 2) = 99.5_sp
b%u(3, 3) = 100.25_dp
print '(a, i0, a, i0, a, f7.2)', 'a: kind ', a%wp, ', cells ', size(a%u), ', max ', max_temperature(a)
print '(a, i0, a, i0, a, f7.2)', 'b: kind ', b%wp, ', cells ', size(b%u), ', max ', max_temperature(b)
end program pdt_portable
Output, identical on GNU Fortran 16.2 and LLVM Flang 23:
a: kind 4, cells 12, max 99.50
b: kind 8, cells 30, max 100.25
Not checked here, because no compiler for them is installed: Intel ifx, NAG, HPE Cray and NVIDIA HPC compilers. See History, Standards and Compilers for the per-compiler support table.
PDTs or something simpler
The reasons the heatflow library does not use a PDT for its grid, in the order they weigh:
| Need | Simpler tool | What a PDT adds |
|---|---|---|
A size that varies per object |
an allocatable component with ordinary |
a size that is part of the type, checked on assignment, but only on GNU Fortran, and not resizable without reallocation of the whole object |
A size known to a procedure |
assumed-shape or explicit-shape dummies and automatic arrays (see Array Arguments: Assumed Shape, Size and Rank) |
the sizes travel with the object so the procedure needs fewer arguments |
One source for several precisions |
a module constant |
a kind parameter, which works everywhere, with one definition of the type and still one specific per kind |
One source for several element types |
not expressible with PDTs; a PDT is parameterized by integers only |
Fortran 2028 templates (a new clause in J3/26-007r1) are meant for this, see What’s New and Fortran 2028 Preview |
A fixed-width character field |
|
a |
A length parameter is the right tool when the size genuinely belongs to the type, such as a fixed-size vector or matrix whose compatibility is checked by the compiler. For a field whose size is read from the configuration at run time, an allocatable array is the same cost and is supported everywhere. The performance consequences of run-time sized components (stack versus heap, descriptors) are in Performance and Memory Layout.
Where the books are outdated
The two Fortran books consulted for this section describe parameterized derived types as a settled Fortran 2003 feature, in the chapter on type parameters and the chapters on object orientation. As rules of the language that is still correct, since the semantics are unchanged in Fortran 2023. What has changed is the context:
| Area | Outdated in the books, current approach |
|---|---|
Compiler support |
the books give no compiler-support information and treat the feature as universally available; on 2026-10-19 one of the two free compilers cannot compile length parameters at all (LLVM Flang 23), and the other has the defects recorded above (GNU Fortran 16.2); programs that must be portable use kind parameters only |
Examples with |
overloading on a kind through a PDT is shown as the main use; modules with |
Next revision |
genericity over types is described as a future direction; Fortran 2028 (J3/26-007r1, still a working draft) adds templates, which cover the case of a type parameterized by another type that PDTs cannot express |
Literal kinds |
|
Initialization |
default initialization of components sized by length parameters is not allowed (C767) |
Sequence types |
Fortran 2003 allowed |
Standard version provenance and compiler support
| Feature | Introduced | Compiler support checked on 2026-10-19 |
|---|---|---|
|
Fortran 2003 (ISO/IEC 1539-1:2004; see 4.3.5 in J3/23-007r1) |
GNU Fortran 16.2 with the defects listed above; LLVM Flang 23 for kind parameters only |
Sequence types with type parameters |
Fortran 2003; not permitted in Fortran 2023 (4.3.5) |
not applicable |
Statement function of a PDT type |
Fortran 2003; not permitted in Fortran 2023 (C1591, 4.3.5) |
not applicable |
|
same Fortran 2003 feature set |
both compilers for kind-only types; GNU Fortran 16.2 stops with an internal compiler error when the hierarchy has length parameters |
Templates (genericity over types) |
Fortran 2028 draft only (J3/26-007r1, clause "Templates, requirements, and instantiation") |
not implemented in either compiler |
Pitfalls
-
Relying on LLVM Flang for length parameters. Flang 23 stops at the first
integer, lentype it has to generate code for. -
Writing
1.5_wpwith a kind parameter. The standard wants a named constant after the underscore; usereal(1.5, wp). -
Initializing an array component sized by a length parameter. C767 forbids it; assign after the declaration.
-
Using
:or*outside their places.:needsallocatableorpointer,*is for dummies, associate names andselect typeguards, and neither is allowed for a kind parameter. -
Passing a fixed length to a binding. The passed-object dummy needs all length parameters assumed; a binding declared with
class(t(3))does not compile. -
Assigning between different lengths. Intrinsic assignment demands equal values unless the target is allocatable with a deferred parameter; the compilers catch it only for constants and not at all at run time.
-
Inquiring a deferred parameter of an unallocated object. The standard forbids it, and the value is not defined.
-
Trusting
b%nxfor an array of PDTs on GNU Fortran 16.2. It is array-valued there, useb(1)%nx. -
Allocating with parameters that disagree with a nondeferred parameter. An error condition in the standard, silent on GNU Fortran 16.2.
-
Polymorphic PDT variables with allocatable components. Crash at
allocateon GNU Fortran 16.2. -
Constructors of extended PDTs. False "initialized twice" errors on GNU Fortran 16.2; assign component by component.
-
move_allocbetween deferred-length PDTs. Segmentation fault at the end of the program on GNU Fortran 16.2. -
bind(c)andsequencetypes. Neither may have type parameters. -
A PDT that does not need to be one. A plain derived type with an allocatable component is usually simpler and works everywhere.
See also Best Practices and Style and Common Gotchas and Pitfalls.