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 * (assumed) or : (deferred)

no, because those are not constant expressions

yes (C702 for :, 7.2 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, field_t(sp) and field_t(dp) are different types (Flang gives each instance its own name)

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 type-param-def-stmt of the same definition, with kind or len, and the list does not repeat a name (C736, C746, C747)

7.5.3.1

A parameter is an integer

the kind selector of the integer in the definition may be given: integer(selected_int_kind(12)), len :: d makes d a 64-bit length parameter; the default is default integer

7.5.3.1

A default value is a constant

wp = dp or n = 3 gives the parameter a default that is used when the derived-type specifier omits it; a parameter without a default must always be given a value (C797)

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 real(wp)

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 type-param-value in a component declaration is a colon or a specification expression of the parameters of the enclosing type (C759); that is how type(field_t(wp, nx, ny)) :: now passes them on

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

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

type(field_t(wp=dp, 3, 4)) :: f (positional after keyword, C798)

Error: Missing keyword name in actual argument list at (1)

error: Type parameter value must have a name, then Type parameter 'nx' lacks a value and has no default and the same for ny

type(field_t(dp, nx=3, nz=4)) :: f (unknown keyword, C799)

Error: The derived parameter 'ny' at (1) does not have a default value, which does not mention nz

error: 'nz' is not the name of a parameter for derived type 'field_t', and Type parameter 'ny' lacks a value and has no default

type(plain_t(3)) :: p for a type without parameters (C796)

Error: Type 'plain_t' is not parameterized and so the type parameter spec list at (1) may not appear

error: Too many type parameters given for derived type 'plain_t'

type(field_t(k, 4, 4)) :: f with a variable k for the kind parameter (C701)

Error: The value for the KIND parameter 'wp' at (1) does not reduce to a constant expression

error: Value of KIND type parameter 'wp' must be constant, followed by the instantiation context

type(field_t(dp, *, 3)) :: f (assumed value on a local, C7100)

Error: The object 'f' at (1) with ASSUMED type parameters must be a dummy or a SELECT TYPE selector(F08/4.2)

error: An assumed (*) type parameter may be used only for a (non-statement function) dummy argument, associate name, character named constant, or external function result

type(field_t(dp, :, 3)) :: f (deferred value without allocatable or pointer, C702)

Error: Entity 'f' at (1) has a deferred LEN parameter 'nx' and requires either the POINTER or ALLOCATABLE attribute

error: 'f' has a type field_t(nx=:,ny=3_4,wp=8_4) with a deferred type parameter but is neither an allocatable nor an object pointer

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 a select type construct, 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 the allocatable or pointer attribute only. The value is given when the object is allocated, by a type-spec in allocate, by source= or mold=, 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_dp and mean_sp receive any extents because they declare * for both length parameters, and the actual argument carries its own values, which the body reads back as f%nx and f%ny. The kind parameter is not assumed: it is written in the dummy’s type, so type(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’s c = make_field(5, 4, 10.0_dp) allocates c with the parameters of the result, because c has 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: c changes from 5 x 4 to 3 x 2 without any deallocate. 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(*)), never class(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 is or class is guard of select type shall 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 dtv argument 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 bind(c)

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 heatflow_step does

18.3.4

Sequence types

Fortran 2003 permitted a sequence type to have type parameters; Fortran 2023 does not (4.3.5)

4.3.5

Statement functions

a statement function shall not be of parameterized derived type (C1591); Fortran 2003 permitted it

15.6.4

select type guards, final subroutines, passed-object dummies, defined I/O

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 k cannot select a type (C701)

7.2

Default initialization

no component whose type parameters or bounds are not constants (C767)

7.5.4.6

type(*) assumed-type dummies

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 (sensor_t, grid_t)

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 not yet implemented: parameterized derived types in code generation (ConvertType.cpp:470); the Flang standards-support table lists "PDT with length type parameters is not supported"

1.5_wp with a kind parameter wp

rejected, "Missing kind-parameter" (C718 requires a named constant)

accepted

real(wp) :: x(3) = 0

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, class is guard with assumed lengths

internal compiler error gfc_get_symbol_decl

not reached

b%nx for an array b

array-valued (must be scalar)

not reached

allocate with parameters differing from nondeferred ones

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 -fcheck=all

not reached

Polymorphic allocatable with an allocatable component

segmentation fault in allocate

works

move_alloc of a deferred-length PDT

segmentation fault at program end

not reached

storage_size of a length-parameterized object

returns 576 bits for four different objects (18_storage_size.f90)

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.

flowchart TD A["Vary a type by precision or by size?"] --> B{"What varies?"} B -->|"precision, a kind"| C["kind parameter: works in GNU Fortran and in Flang"] B -->|"size, an extent or a length"| D{"Must the size be a type parameter?"} D -->|"no"| E["allocatable component or explicit-shape dummy: portable and simple"] D -->|"yes"| F["len parameter: GNU Fortran only, cover each construct with a test"] C --> G["avoid polymorphic variables whose type has allocatable components on GNU Fortran 16.2"]

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 nx, ny components: any compiler, resizable with allocate and move_alloc, can live in a polymorphic variable

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 dp for one precision, generic interfaces for more, or include-file and preprocessor genericity (see Generic and Rank Independent Programming)

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

character(len=n) with an n known to the procedure, or character(len=:) allocatable

a label_t(n) whose length is part of the type

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 kind-based generics

overloading on a kind through a PDT is shown as the main use; modules with selected_real_kind constants and generic interfaces are the common production approach, and a PDT adds only when the type itself, not just a procedure, must be duplicated

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

1.5_wp with a kind type parameter is not valid under C718 (a named constant is required); real(1.5, wp) is

Initialization

default initialization of components sized by length parameters is not allowed (C767)

Sequence types

Fortran 2003 allowed sequence types with parameters; Fortran 2023 does not (4.3.5)

Standard version provenance and compiler support

Feature Introduced Compiler support checked on 2026-10-19

kind and len parameters, defaults, type-param-spec lists with keywords, structure constructors with parameters, %kind/%len inquiry, assumed and deferred length parameters

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

select type with class is (t(*)) guards on a PDT

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, len type it has to generate code for.

  • Writing 1.5_wp with a kind parameter. The standard wants a named constant after the underscore; use real(1.5, wp).

  • Initializing an array component sized by a length parameter. C767 forbids it; assign after the declaration.

  • Using : or * outside their places. : needs allocatable or pointer, * is for dummies, associate names and select type guards, 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%nx for an array of PDTs on GNU Fortran 16.2. It is array-valued there, use b(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 allocate on GNU Fortran 16.2.

  • Constructors of extended PDTs. False "initialized twice" errors on GNU Fortran 16.2; assign component by component.

  • move_alloc between deferred-length PDTs. Segmentation fault at the end of the program on GNU Fortran 16.2.

  • bind(c) and sequence types. 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.