Generic and Rank Independent Programming

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-18. Every example was compiled warning-free with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2) and run, and also compiled and run with LLVM Flang 23; the output shown is real output. The exceptions are the Fortran 2023 features typeof, classof and the rank clause, which GNU Fortran 16.2 rejects and which are therefore labelled as checked with Flang only, and the Fortran 2023 uses of integer arrays as subscripts and bounds, which neither compiler accepts: those fragments are labelled unchecked and show the real diagnostics of both compilers. Examples that are deliberately wrong or that deliberately fail at run time are labelled as such. The clauses cited are those of J3/23-007r1: types are in clause 7.3, array declarations in 8.5.8 and 8.5.17, array sections in 9.5.3, allocate in 9.7.1, pointer assignment in 10.2.2, select rank and select type in 11.1.10 and 11.1.11, generic interfaces in 15.4.3.4, and elemental procedures in 15.9.

Fortran has no template or generic-programming facility in the standard of 2023. What it has is a set of smaller tools, each covering one axis of variation, and one older and one new set of features for the axis that matters most in numerical code, the rank of an array. This page covers what varies (a kind, a rank, a type), the tool that covers each, what each costs, and what the Fortran 2023 additions change and which compilers accept them. The templates planned for Fortran 2028 are only previewed here; see What’s New and Fortran 2028 Preview. Background pages: Interfaces, Generics and Operator Overloading (generic resolution and distinguishability), Array Arguments: Assumed Shape, Size and Rank (assumed-shape, assumed-size and assumed-rank dummies in the usual setting), and Object Oriented Programming (select type). The same problem in other languages is covered by the C++ templates, the Rust generics, the Go generics and C type-generic programming pages.

Tool Covers Since Clause (J3/23-007r1) gfortran 16.2 and Flang 23

Generic interface with module procedure

kind, rank, type (one specific each)

Fortran 90

15.4.3.4

both accept

elemental procedure

every rank, one body

Fortran 95

15.9

both accept

class(*) with select type

any type, at run time

Fortran 2003

7.3.2.3, 11.1.11

both accept

include file or preprocessor template

kind, rank, type, as text

the include line is standard; the preprocessor is not

6.4

both accept

Assumed-rank (..) with select rank

every rank

Fortran 2018

8.5.8.7, 11.1.10

both accept

typeof and classof

kind, type and type parameters follow another entity

Fortran 2023

7.3.2.1

Flang accepts, gfortran rejects

rank clause

rank given by a constant expression

Fortran 2023

8.5.17

Flang accepts, gfortran rejects

@ multiple subscripts and triplets

rank-independent indexing and sectioning

Fortran 2023

9.5.3.2

both reject

Bounds and shapes from integer arrays (declarations, allocate, pointer assignment)

rank-independent shapes

Fortran 2023

8.5.8.2, 9.7.1.1, 10.2.2.3

both reject

c_f_pointer with lower=

rank-one view with arbitrary bounds

Fortran 2023

18.2.3.3

both accept

Templates and generic subprograms

all of the above

Fortran 2028 draft

not in J3/23-007r1

not available

Techniques for generic Fortran code against what varies: kind, rank and type. Generic interfaces need one specific per case. Elemental procedures and assumed-rank dummies cover every rank. Unlimited polymorphism covers any listed type at run time. Include files and preprocessor templates cover everything as text. The Fortran 2023 typeof, classof and rank clause follow another entity. The at subscripts and Fortran 2028 templates cannot be compiled today.

What varies: kind, rank and type

Three things can differ between two uses of a computation, and Fortran handles them differently.

  • Kind. real(sp) against real(dp): the same source text with a different kind parameter. The standard gives no way to write the kind as a parameter of a procedure (derived types can have kind parameters, see Parameterized Derived Types), so each kind needs its own copy of the code. The usual answer is a generated or hand-written copy behind a generic name (below).

  • Rank. A scalar, a vector, a matrix. Before Fortran 2018 a procedure had a fixed rank for each dummy argument, and the same code for ranks 1 to 7 meant seven specifics. elemental removes the problem for pointwise operations, assumed-rank (Fortran 2018) for the rest.

  • Type. integer, real, a derived type. Unlimited polymorphism (class(*)) gives a run-time answer and has a cost; the other answer is again a copy per type.

A fourth thing, which these tools do not provide, is a constraint on the type parameter, the equivalent of a C++ concept, a Rust trait bound or a Go constraint: a Fortran generic simply fails to compile, or fails to resolve, when a specific does not fit. The Fortran 2028 draft adds requirements for this purpose (see the last section).

Generic interfaces: one specific per case

A generic interface (15.4.3.4) gives several procedures one name; a reference is resolved at compile time from the types, kinds and ranks of the actual arguments (15.5.5.2), so there is no run-time cost. The standard requires the specifics to be distinguishable (15.4.3.4.5); a different rank is enough, and so is a different kind. That is exactly the tool for heatflow’s norms: the next program has a generic l2_norm over two kinds and two ranks, and therefore four specifics, each a copy of the same single line of arithmetic.

module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real32, real64
  implicit none
  private
  public :: sp, dp
  integer, parameter :: sp = real32
  integer, parameter :: dp = real64
end module heatflow_kinds

module heatflow_norms
  use heatflow_kinds, only: sp, dp
  implicit none
  private
  public :: l2_norm

  ! One public name, one specific per (kind, rank) pair: 2 kinds x 2 ranks.
  interface l2_norm
    module procedure l2_norm_sp_1, l2_norm_dp_1, l2_norm_sp_2, l2_norm_dp_2
  end interface l2_norm

contains

  pure function l2_norm_sp_1(u) result(r)
    real(sp), intent(in) :: u(:)
    real(sp) :: r
    r = sqrt(sum(u**2))
  end function l2_norm_sp_1

  pure function l2_norm_dp_1(u) result(r)
    real(dp), intent(in) :: u(:)
    real(dp) :: r
    r = sqrt(sum(u**2))
  end function l2_norm_dp_1

  pure function l2_norm_sp_2(u) result(r)
    real(sp), intent(in) :: u(:,:)
    real(sp) :: r
    r = sqrt(sum(u**2))
  end function l2_norm_sp_2

  pure function l2_norm_dp_2(u) result(r)
    real(dp), intent(in) :: u(:,:)
    real(dp) :: r
    r = sqrt(sum(u**2))
  end function l2_norm_dp_2
end module heatflow_norms

program ex01_generic_by_kind_and_rank
  use heatflow_kinds, only: sp, dp
  use heatflow_norms, only: l2_norm
  implicit none
  real(dp) :: field(2,2)
  real(sp) :: row(3)
  field = reshape([3.0_dp, 0.0_dp, 0.0_dp, 4.0_dp], [2, 2])
  row = [1.0_sp, 2.0_sp, 2.0_sp]
  print '(a, f6.3)', 'rank 2, dp: ', l2_norm(field)
  print '(a, f6.3)', 'rank 1, sp: ', l2_norm(row)
  print '(a, f6.3)', 'rank 1, dp: ', l2_norm(real(row, dp))
  ! l2_norm(real(1, dp)) would be rejected: no specific accepts a scalar.
end program ex01_generic_by_kind_and_rank

The output, identical with both compilers:

rank 2, dp:  5.000
rank 1, sp:  3.000
rank 1, dp:  3.000

The cost is the number of specifics, which is the product of the number of kinds, the number of ranks and the number of types supported. Supporting real32, real64 and real128 for ranks 1 to 7 is 21 procedures per operation, which is why the widely used numerical libraries generate these files from a template. The commented last line of the program shows that a reference with no matching specific is a compile-time error, not a run-time one.

Elemental procedures and class(*)

An elemental procedure (15.9) is written for scalar dummy arguments and may be called with array actual arguments of any rank, all of the same shape; the operation is applied to each element. It is pure unless declared impure elemental, so it is the cheapest way to cover every rank, and the right one for pointwise code, a conversion or a function like sin. It does not help with reductions, norms or anything that needs to see the whole array. The next program shows an elemental Celsius-to-Kelvin conversion applied to a scalar, a vector and a matrix, and the other classical answer, an unlimited polymorphic dummy (class(*), 7.3.2.3), which accepts an actual argument of any type and branches on its dynamic type with select type (11.1.11).

module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real32, real64, int32
  implicit none
  private
  public :: sp, dp, ip
  integer, parameter :: sp = real32, dp = real64, ip = int32
end module heatflow_kinds

module heatflow_unlimited
  use heatflow_kinds, only: sp, dp, ip
  implicit none
  private
  public :: celsius_to_kelvin, describe

contains

  ! elemental: written for scalars, works for every rank (and is pure).
  elemental function celsius_to_kelvin(t) result(k)
    real(dp), intent(in) :: t
    real(dp) :: k
    k = t + 273.15_dp
  end function celsius_to_kelvin

  ! class(*): one specific for every type, at the price of select type
  ! and of a run-time dispatch.
  function describe(x) result(s)
    class(*), intent(in) :: x
    character(:), allocatable :: s
    character(len=32) :: buf
    select type (x)
    type is (integer(ip))
      write (buf, '(i0)') x
      s = 'integer ' // trim(buf)
    type is (real(sp))
      write (buf, '(f0.2)') x
      s = 'real(sp) ' // trim(buf)
    type is (real(dp))
      write (buf, '(f0.2)') x
      s = 'real(dp) ' // trim(buf)
    type is (character(*))
      s = 'character "' // x // '"'
    class default
      s = 'unsupported type'
    end select
  end function describe
end module heatflow_unlimited

program ex02_elemental_and_class_star
  use heatflow_kinds, only: sp, dp, ip
  use heatflow_unlimited, only: celsius_to_kelvin, describe
  implicit none
  real(dp) :: t0, t1(3), t2(2,2)
  t0 = 20.0_dp
  t1 = [0.0_dp, 20.0_dp, 100.0_dp]
  t2 = reshape([0.0_dp, 10.0_dp, 20.0_dp, 30.0_dp], [2, 2])
  print '(f7.2)', celsius_to_kelvin(t0)
  print '(3f8.2)', celsius_to_kelvin(t1)
  print '(2f8.2)', celsius_to_kelvin(t2)
  print '(a)', describe(42_ip)
  print '(a)', describe(1.5_sp)
  print '(a)', describe(2.5_dp)
  print '(a)', describe('heat')
  print '(a)', describe(.true.)
end program ex02_elemental_and_class_star
 293.15
  273.15  293.15  373.15
  273.15  283.15
  293.15  303.15
integer 42
real(sp) 1.50
real(dp) 2.50
character "heat"
unsupported type

The class(*) version is not generic in the compile-time sense. The set of types is closed by the select type, the dispatch happens at run time, the elements of an array cannot be handled without a rank-specific branch, and an unlimited polymorphic actual argument has no kind to take: a procedure that wants real(sp) and real(dp) still has to list both. It is the right tool for heterogeneous containers and for callbacks that carry an opaque user context (the Fortran equivalent of void *), and the wrong one for numerical kernels.

Include files and preprocessor templates

The standard has an include line (6.4): the text of a file replaces the line before the program is processed. It is not a statement and cannot be conditional, but it is enough for a template: a file written with a kind parameter that is defined by the including scope. The next program includes the same file stats_body.inc into two modules, one that renames sp to wp and one that renames dp to wp; a third module merges the two specifics under one generic name. The include file is:

! stats_body.inc -- included inside a module that has defined the kind "wp".
pure function mean_1d(u) result(m)
  real(wp), intent(in) :: u(:)
  real(wp) :: m
  m = sum(u) / real(size(u), wp)
end function mean_1d

pure function range_1d(u) result(r)
  real(wp), intent(in) :: u(:)
  real(wp) :: r
  r = maxval(u) - minval(u)
end function range_1d
module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real32, real64
  implicit none
  private
  public :: sp, dp
  integer, parameter :: sp = real32, dp = real64
end module heatflow_kinds

! The same body is compiled twice, once per kind: the include file is the template.
module heatflow_stats_sp
  use heatflow_kinds, only: wp => sp
  implicit none
  private
  public :: mean_1d, range_1d
contains
  include 'stats_body.inc'
end module heatflow_stats_sp

module heatflow_stats_dp
  use heatflow_kinds, only: wp => dp
  implicit none
  private
  public :: mean_1d, range_1d
contains
  include 'stats_body.inc'
end module heatflow_stats_dp

! A third module renames the clashing specifics and merges them into generics.
module heatflow_stats
  use heatflow_stats_sp, only: mean_sp => mean_1d, range_sp => range_1d
  use heatflow_stats_dp, only: mean_dp => mean_1d, range_dp => range_1d
  implicit none
  private
  public :: mean, range_of
  interface mean
    module procedure mean_sp, mean_dp
  end interface mean
  interface range_of
    module procedure range_sp, range_dp
  end interface range_of
end module heatflow_stats

program ex03_include_template
  use heatflow_kinds, only: sp, dp
  use heatflow_stats, only: mean, range_of
  implicit none
  real(sp) :: a(4) = [1.0_sp, 2.0_sp, 3.0_sp, 6.0_sp]
  real(dp) :: b(3) = [10.0_dp, 20.0_dp, 60.0_dp]
  print '(2(a, f6.2))', 'sp mean=', mean(a), '  range=', range_of(a)
  print '(2(a, f6.2))', 'dp mean=', mean(b), '  range=', range_of(b)
end program ex03_include_template
sp mean=  3.00  range=  5.00
dp mean= 30.00  range= 50.00

Two more points. First, because each module compiles the included text separately, the names must not collide when the two modules are used together; the renaming mean_sp => mean_1d in the merging module is how the program above avoids a clash. Second, the build system must know that every file that includes stats_body.inc depends on it; fpm and CMake do track include files, a hand-written Makefile has to say so (see Building with Make, CMake and Meson).

The other route is the preprocessor. It is not part of the Fortran standard, but every compiler runs a C-style preprocessor on files whose suffix is .F90 (capital F), or on every file with -cpp. A macro can then generate a whole procedure for each kind:

! Preprocessor genericity: the file suffix .F90 makes the compiler run cpp first.
module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real32, real64
  implicit none
  private
  public :: sp, dp
  integer, parameter :: sp = real32, dp = real64
end module heatflow_kinds

module heatflow_scale
  use heatflow_kinds, only: sp, dp
  implicit none
  private
  public :: scaled
  interface scaled
    module procedure scaled_sp, scaled_dp
  end interface scaled

#define DEFINE_SCALED(NAME, KIND) \
  pure function NAME(u, f) result(r); \
    real(KIND), intent(in) :: u(:), f; \
    real(KIND) :: r(size(u)); \
    r = f * u; \
  end function NAME

contains
  DEFINE_SCALED(scaled_sp, sp)
  DEFINE_SCALED(scaled_dp, dp)
end module heatflow_scale

program ex04_cpp_template
  use heatflow_kinds, only: sp, dp
  use heatflow_scale, only: scaled
  implicit none
  print '(3f6.1)', scaled([1.0_sp, 2.0_sp, 3.0_sp], 2.0_sp)
  print '(3f6.1)', scaled([1.0_dp, 2.0_dp, 3.0_dp], 0.5_dp)
end program ex04_cpp_template
   2.0   4.0   6.0
   0.5   1.0   1.5

A preprocessor template is the most flexible form of genericity and the least checked one: error messages refer to the expanded text, editors and other tools cannot parse the macro, and cpp is a text tool that was designed for C, so how it treats Fortran-specific text (continuation lines, //, the case of names) differs from one compiler to another. Keep macros small, and put the logic in ordinary Fortran. The generated approach is also common: fortran-lang’s stdlib keeps its numerical procedures in .fypp templates processed by the Fypp preprocessor and ships the generated .F90; see Fortran stdlib.

Tool Checked at compile time Cost Typical use

Generic interface

yes, per specific

one procedure per kind, rank, type

small libraries, wrappers

elemental

yes

none; one body

pointwise functions

class(*)

partly: the branch list is checked, the type is not

run-time dispatch, boxing

opaque user context, containers

include template

yes, per instantiation

text copy per instantiation; build dependency

numerical kernels, a few kinds

Preprocessor template

after expansion

text copy; poor diagnostics and tooling

code generators, large matrices of kinds and ranks

Assumed-rank dummy arguments and select rank

Fortran 2018 added the one feature that really is rank-independent, the assumed-rank dummy argument. It is declared with (..) (8.5.8.7); an actual argument of any rank, including a scalar, may correspond to it (15.5.2.4), and the dummy takes the rank and the extents of the actual argument, with every lower bound equal to one. Constraint C840 says what the dummy may be used for: it can be passed to another assumed-rank dummy, be the first argument of an inquiry function (rank, size, shape, lbound, ubound, is_contiguous, storage_size, …​), be the argument of c_loc or c_sizeof from iso_c_binding, or be the selector of select rank. It may not be subscripted, used in an expression, or assigned: the elements are reached by narrowing the rank with select rank (11.1.10), which selects one block by the rank of the selector and, in that block, gives the name the selected rank.

The next program does both. summarize only inquires, so one body handles every rank; total needs the elements and has to list the ranks it supports, rank default catching the rest.

module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  public :: dp
  integer, parameter :: dp = real64
end module heatflow_kinds

module heatflow_inspect
  use heatflow_kinds, only: dp
  implicit none
  private
  public :: summarize, total

contains

  ! An assumed-rank dummy has the rank of its actual argument; the rank can be
  ! queried, but the elements can only be reached through select rank.
  subroutine summarize(a)
    real(dp), intent(in) :: a(..)
    integer :: k
    print '(a, i0, a, i0)', 'rank=', rank(a), ' size=', size(a)
    do k = 1, rank(a)
      print '(a, i0, a, i0, a, i0)', '  dim ', k, ': lbound=', lbound(a, k), ' extent=', size(a, k)
    end do
  end subroutine summarize

  function total(a) result(s)
    real(dp), intent(in) :: a(..)
    real(dp) :: s
    select rank (a)
    rank (0)
      s = a
    rank (1)
      s = sum(a)
    rank (2)
      s = sum(a)
    rank (3)
      s = sum(a)
    rank default
      error stop 'total: rank above 3 is not supported'
    end select
  end function total
end module heatflow_inspect

program ex05_assumed_rank
  use heatflow_kinds, only: dp
  use heatflow_inspect, only: summarize, total
  implicit none
  real(dp) :: scalar, line(5), plane(3, 4), block(2, 2, 2)
  real(dp), allocatable :: w(:, :)
  scalar = 7.0_dp
  line = 1.0_dp
  plane = 2.0_dp
  block = 0.5_dp
  allocate (w(0:2, -1:1))
  w = 1.0_dp
  call summarize(scalar)
  call summarize(plane)
  call summarize(w)
  call summarize(line(2:4))
  print '(4(a, f6.2))', 'scalar ', total(scalar), '  line ', total(line), '  plane ', total(plane), '  block ', total(block)
end program ex05_assumed_rank
rank=0 size=1
rank=2 size=12
  dim 1: lbound=1 extent=3
  dim 2: lbound=1 extent=4
rank=2 size=9
  dim 1: lbound=1 extent=3
  dim 2: lbound=1 extent=3
rank=1 size=3
  dim 1: lbound=1 extent=3
scalar   7.00  line   5.00  plane  24.00  block   4.00

Notes on the output. The scalar has rank 0 and size 1; an array section line(2:4) has rank 1 and extent 3, and the dummy sees it with lower bound 1 even though the allocatable w was declared with bounds 0:2 and -1:1; the bounds of the actual argument are not passed for a nonallocatable, nonpointer assumed-rank dummy (15.5.2.4). An assumed-rank dummy with the allocatable or pointer attribute is the exception: it has the bounds of the actual argument (8.5.8.4).

The select rank construct accepts three kinds of case: rank (n) for a rank n constant expression, rank (*) for an assumed-size array (the selector is then of rank one), and rank default, which matches any other rank and leaves the selector assumed-rank. At most one case per rank, one rank (*) and one rank default are allowed (C1157, C1158); a rank (*) is not allowed when the selector is allocatable or a pointer (C1160). A rank above the maximum of 15 is allowed in a case but never selected. A rank (n) case never matches an assumed-size actual argument; only rank (*) does, and in its block the name is a rank-one assumed-size array. If no case matches and there is no rank default, control passes to the end select without executing any block (11.1.10.2), silently, so give every select rank a rank default that either handles the case or stops.

Misusing an assumed-rank dummy

The first program is deliberately non-conforming: it subscripts an assumed-rank dummy. GNU Fortran 16.2 refuses it at compile time; Flang 23 also rejects it, although its message reads as if the dummy were an array of a fixed rank.

! NON-CONFORMING ON PURPOSE: an assumed-rank dummy cannot be subscripted.
program err08_assumed_rank_element
  implicit none
  real :: a(2, 2)
  a = 1.0
  print *, first(a)
contains
  function first(x) result(r)
    real, intent(in) :: x(..)
    real :: r
    r = x(1, 1)
  end function first
end program err08_assumed_rank_element
err08_assumed_rank_element.f90:11:8:

   11 |     r = x(1, 1)
      |        1
Error: Assumed-rank variable x at (1) may only be used as actual argument

Flang 23:

error: Semantic errors in err08_assumed_rank_element.f90
err08_assumed_rank_element.f90:11:9: error: Reference to rank-1 object 'x' has 2 subscripts
      r = x(1, 1)
          ^^^^^^^

The second is deliberately non-conforming for the same constraint (C840): the dummy is used as an operand.

! NON-CONFORMING ON PURPOSE: an assumed-rank dummy cannot be an argument of sum-like elemental code.
program err10_noselect
  implicit none
  real :: a(2, 2)
  a = 1.0
  print *, twice(a)
contains
  function twice(x) result(r)
    real, intent(in) :: x(..)
    real :: r(size(x))
    r = 2.0 * x
  end function twice
end program err10_noselect
err10_noselect.f90:11:15:

   11 |     r = 2.0 * x
      |               1
Error: Assumed-rank variable x at (1) may only be used as actual argument

Flang 23:

error: Semantic errors in err10_noselect.f90
err10_noselect.f90:11:9: error: An assumed-rank dummy argument is not allowed as an operand here
      r = 2.0 * x
          ^^^^^^^

The third is a run-time failure on purpose: select rank has branches for ranks 1 and 2 and a rank default that stops, and the program calls it with a rank-4 array.

! Run-time failure on purpose: select rank without a branch for the actual rank.
program err09_rank_default
  implicit none
  real :: a(2, 2, 2, 2)
  a = 1.0
  print *, total(a)
contains
  function total(x) result(s)
    real, intent(in) :: x(..)
    real :: s
    select rank (x)
    rank (1)
      s = sum(x)
    rank (2)
      s = sum(x)
    rank default
      error stop 'total: unsupported rank'
    end select
  end function total
end program err09_rank_default

GNU Fortran 16.2 prints the message and a backtrace and ends the program with exit status 1 (the addresses in the backtrace differ from run to run, so only the message is shown):

ERROR STOP total: unsupported rank

Error termination. Backtrace:

Flang 23 prints:

Fortran ERROR STOP: total: unsupported rank

Rank-independent code without select rank

select rank only helps when each rank has its own body. The common case is code that does not care about the rank, that is elementwise or a reduction over every element. For that, view the argument as one long vector and work on the vector: the dummy must be contiguous, and c_loc (which accepts an assumed-rank argument, C840) and c_f_pointer do the reshaping. The pointer is a rank-one view of the same memory, so the operation is in place.

module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  public :: dp
  integer, parameter :: dp = real64
end module heatflow_kinds

module heatflow_flat
  use, intrinsic :: iso_c_binding, only: c_loc, c_f_pointer
  use heatflow_kinds, only: dp
  implicit none
  private
  public :: scale_any_rank

contains

  ! Rank-independent, in place: view the (contiguous) argument as a rank-one array.
  subroutine scale_any_rank(a, factor)
    real(dp), target, contiguous, intent(inout) :: a(..)
    real(dp), intent(in) :: factor
    real(dp), pointer, contiguous :: flat(:)
    call c_f_pointer(c_loc(a), flat, [size(a)])
    flat = factor * flat
  end subroutine scale_any_rank
end module heatflow_flat

program ex06_flatten
  use heatflow_kinds, only: dp
  use heatflow_flat, only: scale_any_rank
  implicit none
  real(dp) :: v(3), m(2, 2), t(2, 2, 2)
  v = 1.0_dp
  m = reshape([1.0_dp, 2.0_dp, 3.0_dp, 4.0_dp], [2, 2])
  t = 2.0_dp
  call scale_any_rank(v, 10.0_dp)
  call scale_any_rank(m, 10.0_dp)
  call scale_any_rank(t, 10.0_dp)
  print '(3f6.1)', v
  print '(2f6.1)', m
  print '(8f6.1)', t
end program ex06_flatten
  10.0  10.0  10.0
  10.0  20.0
  30.0  40.0
  20.0  20.0  20.0  20.0  20.0  20.0  20.0  20.0

Three conditions apply. The dummy needs the target attribute, because c_loc needs a target, and contiguous, because c_loc requires a contiguous array (18.2.3.7). Passing a noncontiguous section, such as the row m(2, :) of a 3 by 3 matrix, makes the compiler copy it in and out, so the result is still correct for intent(inout), at the price of a temporary; gfortran with -fcheck=all reports it at run time as Fortran runtime warning: An array temporary was created, and Flang 23 says nothing. And the argument cannot be a polymorphic object or of a non-interoperable type for c_loc, which is a restriction of the technique, not of assumed rank. Where the standard of an older version applies, a classic alternative is reshape, which copies; the c_f_pointer view does not.

typeof and classof

Fortran 2023 adds two type specifiers (7.3.2.1) that name the type of another entity instead of repeating its declaration: typeof (data-ref) declares an entity of the same type and type parameters as the data-ref, and classof (data-ref) declares a polymorphic entity of the same declared type. They may be used in type declaration statements and component definitions, and the data-ref must be declared before. The rules are constraints C708 to C713:

  • an entity declared with class or classof is a dummy argument or has the allocatable or pointer attribute (C708);

  • typeof and classof appear only in a type declaration statement or component definition (C709);

  • the data-ref has its type and type parameters declared earlier (C710);

  • typeof does not accept an unlimited polymorphic or abstract data-ref, and classof does not accept an assumed-type or intrinsic-type one (C711, C712);

  • for an optional data-ref neither may have a deferred or assumed type parameter (C713).

Only the type and type parameters are taken: the rank, the allocatable, pointer and intent attributes are not (the data-ref may be an array, and a typeof of it is a scalar), and if the data-ref has an assumed type parameter the new entity has the same value, not an assumed one (Note 2). The use is exactly the one that the kind axis needs: the kind of a temporary, an accumulator or a function result follows a dummy argument, and with a generic and a macro-free source the declarations of the specifics cannot get out of step.

GNU Fortran 16.2 does not accept these specifiers; LLVM Flang 23 does. The next program was therefore compiled and run with Flang 23 only.

module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real32, real64
  implicit none
  private
  public :: sp, dp
  integer, parameter :: sp = real32, dp = real64
end module heatflow_kinds

module heatflow_typeof
  use heatflow_kinds, only: sp, dp
  implicit none
  private
  public :: grid_t, mean_of, clone

  type :: grid_t
    integer :: nx = 0
    real(dp), allocatable :: u(:, :)
  end type grid_t

  interface mean_of
    module procedure mean_sp, mean_dp
  end interface mean_of

contains

  ! typeof(u): the accumulator and the result follow the kind of u. The
  ! declaration is written once; only the dummy argument names the kind.
  function mean_sp(u) result(m)
    real(sp), intent(in) :: u(:)
    typeof(u) :: m
    typeof(u) :: acc
    acc = sum(u)
    m = acc / size(u)
  end function mean_sp

  function mean_dp(u) result(m)
    real(dp), intent(in) :: u(:)
    typeof(u) :: m
    typeof(u) :: acc
    acc = sum(u)
    m = acc / size(u)
  end function mean_dp

  ! classof(g): polymorphic with the declared type of g; with class(grid_t)
  ! this would be written twice, here it follows the argument.
  function clone(g) result(copy)
    class(grid_t), intent(in) :: g
    classof(g), allocatable :: copy
    allocate (copy, source=g)
  end function clone
end module heatflow_typeof

program ex07_typeof_classof
  use heatflow_kinds, only: sp, dp
  use heatflow_typeof, only: grid_t, mean_of, clone
  implicit none
  type(grid_t) :: g
  class(grid_t), allocatable :: h
  g%nx = 2
  allocate (g%u(2, 2))
  g%u = 5.0_dp
  h = clone(g)
  h%u = 1.0_dp
  print '(a, f5.1, a, f5.1)', 'original ', g%u(1, 1), '  clone ', h%u(1, 1)
  print '(a, f5.2)', 'mean sp ', mean_of([1.0_sp, 2.0_sp, 6.0_sp])
  print '(a, f5.2)', 'mean dp ', mean_of([1.0_dp, 2.0_dp, 6.0_dp])
end program ex07_typeof_classof

Output with Flang 23:

original   5.0  clone   1.0
mean sp  3.00
mean dp  3.00

GNU Fortran 16.2 stops at the first use of typeof, with a message that does not name the feature:

! F2023 typeof: accepted by Flang 23, rejected by gfortran 16.2.
program err05_typeof
  implicit none
  real(kind(1.0d0)) :: x(3) = [1.0d0, 2.0d0, 3.0d0]
  typeof(x) :: y
  y = x(2)
  print *, y
end program err05_typeof
err05_typeof.f90:5:7:

    5 |   typeof(x) :: y
      |       1
Error: Mangled derived type definition at (1)
err05_typeof.f90:6:3:

    6 |   y = x(2)
      |   1
Error: Symbol 'y' at (1) has no IMPLICIT type

and at classof, where it does not even recognise a statement:

! F2023 classof: accepted by Flang 23, rejected by gfortran 16.2.
module shapes
  implicit none
  type :: shape_t
    real :: s = 1.0
  end type shape_t
contains
  function clone(a) result(b)
    class(shape_t), intent(in) :: a
    classof(a), allocatable :: b
    allocate (b, source=a)
  end function clone
end module shapes
err06_classof.f90:10:5:

   10 |     classof(a), allocatable :: b
      |     1
Error: Unclassifiable statement at (1)
err06_classof.f90:11:15:

   11 |     allocate (b, source=a)
      |               1
Error: Allocate-object at (1) is neither a data pointer nor an allocatable variable
err06_classof.f90:8:29:

    8 |   function clone(a) result(b)
      |                             1
Error: Result 'b' of contained function 'clone' at (1) has no IMPLICIT type

Flang 23 compiled the module above (flang-23 -c) without messages. The Fortran 2023 text of Note 2 and the constraints are in 7.3.2.1; Flang’s own status table lists typeof and classof as supported. Until gfortran accepts them, keep the kind in a named constant of a kinds module and use that, as the other pages of this section do.

The rank clause

The second Fortran 2023 addition to declarations is the rank clause (8.5.17, R829): rank (scalar-int-constant-expr) gives an entity its rank without writing a list of colons, and the expression may use the rank intrinsic of another entity. It is an alternative to the dimension attribute. An entity declared with it is a dummy data object or has the allocatable or pointer attribute (C864), and the expression is nonnegative and not greater than the maximum rank (C863):

  • rank zero gives a scalar;

  • with allocatable or pointer the entity is a deferred-shape array of that rank;

  • otherwise (a dummy argument) it is an assumed-shape array of that rank with all lower bounds equal to one.

Together with typeof it lets a declaration follow another entity completely, typeof (a), rank (rank (a)), allocatable :: b, without a macro or a list of colons that has to be edited when the rank changes. The next program, again Flang only, declares a dummy of rank two and an allocatable result with the same rank.

module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  public :: dp
  integer, parameter :: dp = real64
end module heatflow_kinds

module heatflow_rank
  use heatflow_kinds, only: dp
  implicit none
  private
  public :: relabel

contains

  ! rank(rank(ref)): the dummy and the allocatable result have the rank of ref,
  ! decided by a constant expression instead of a column of colons.
  subroutine relabel(ref, copy)
    real(dp), rank(2), intent(in) :: ref
    real(dp), rank(rank(ref)), allocatable, intent(out) :: copy
    allocate (copy, mold=ref)
    copy = -ref
  end subroutine relabel
end module heatflow_rank

program ex08_rank_clause
  use heatflow_kinds, only: dp
  use heatflow_rank, only: relabel
  implicit none
  real(dp) :: u(0:1, 0:2)
  real(dp), allocatable :: v(:, :)
  u = 3.0_dp
  call relabel(u, v)
  print '(a, 2i2, a, f5.1)', 'shape(v) =', shape(v), '  v(1,1) =', v(1, 1)
end program ex08_rank_clause

Output with Flang 23:

shape(v) = 2 3  v(1,1) = -3.0

GNU Fortran 16.2 rejects the clause as an invalid name after the attribute comma:

! F2023 rank clause: accepted by Flang 23, rejected by gfortran 16.2.
program err07_rank_clause
  implicit none
  real, rank(3), allocatable :: x
  allocate (x(2, 2, 2))
  print *, rank(x)
end program err07_rank_clause
err07_rank_clause.f90:4:8:

    4 |   real, rank(3), allocatable :: x
      |        1
Error: Invalid character in name at (1)
err07_rank_clause.f90:5:13:

    5 |   allocate (x(2, 2, 2))
      |             1
Error: Allocate-object at (1) is neither a data pointer nor an allocatable variable
err07_rank_clause.f90:6:17:

    6 |   print *, rank(x)
      |                 1
Error: Symbol 'x' at (1) has no IMPLICIT type
err07_rank_clause.f90:6:17:

    6 |   print *, rank(x)
      |                 1
Error: Symbol 'x' at (1) has no IMPLICIT type

Flang 23 accepts it and prints 3. The rank clause is the Fortran 2023 item that Flang’s table marks as supported ("Using an integer constant to specify rank").

Two remarks. The rank of a scalar is 0, so rank (rank (n)) for a scalar n declares a scalar; code that expects a rank-one array there (for instance a bounds vector sized by rank(n)) ends up with a zero-sized or scalar entity, which is a classic mistake. And ubound takes the dimension as its own argument: the correct call is ubound (a, rank (a)), not a section.

Integer arrays as subscripts, bounds and shapes

The remaining Fortran 2023 items let a rank-one integer array stand for a list of scalar expressions, so that code which manipulates a rank only through a vector of subscripts or extents does not depend on that rank. The syntax in the standard (clause numbers refer to J3/23-007r1) is:

Place Form (the arrays have rank one and constant size equal to the rank of the object) Clause

Array element or section

a(@idx) replaces the subscripts a(idx(1), idx(2), ...); a(@lo:hi:st) replaces the triplets; in a triplet a scalar part is broadcast

9.5.3.1, 9.5.3.2

Explicit-shape and assumed-shape declarations

real :: x(shape(w)) for the extents, real :: y(lb:ub) with arrays for both bounds, real :: z(lb:) for an assumed-shape lower bounds

8.5.8.2, 8.5.8.3

allocate

allocate (a(ub)) and allocate (a(lb:ub)) with arrays instead of a list of bounds

9.7.1.1, 9.7.1.2

Pointer assignment

p(lb:) => t and p(lb:ub) => t, bounds-remapping from arrays

10.2.2.2, 10.2.2.3

A multiple subscript expands in place to as many subscripts as the array has elements, so a(@[2, 3, 1]) is a(2, 3, 1) and a @ list sits between ordinary subscripts. Triplets expand the same way; an omitted first or second array takes the lower or upper bounds of the corresponding dimensions. The point of all this is that a procedure with an assumed-rank argument can index it, which select rank could do only one rank at a time.

Neither GNU Fortran 16.2 nor Flang 23 accepts any of them. Flang’s standards table marks "Using integer arrays to specify subscripts" and "Using integer arrays to specify rank and bound of an array" as not implemented, and the diagnostics confirm it. The four fragments below were therefore not compiled successfully with either compiler: they are unchecked against the syntax rules in J3/23-007r1, and are shown with the real diagnostics of both compilers. Each is a complete program.

Multiple subscripts (9.5.3.2):

! UNCHECKED F2023 SYNTAX: multiple subscripts (9.5.3.2). Rejected by gfortran 16.2 and Flang 23.
program err01_at_subscript
  implicit none
  real :: a(3, 3, 3)
  integer, parameter :: idx(3) = [2, 3, 1]
  a = 0.0
  a(@idx) = 1.0
  print *, sum(a(@[1, 1]:[2, 2], 1))
end program err01_at_subscript

gfortran 16.2:

err01_at_subscript.f90:7:5:

    7 |   a(@idx) = 1.0
      |     1
Error: Expected array subscript at (1)
err01_at_subscript.f90:8:18:

    8 |   print *, sum(a(@[1, 1]:[2, 2], 1))
      |                  1
Error: Expected array subscript at (1)

Flang 23:

error: Semantic errors in err01_at_subscript.f90
err01_at_subscript.f90:7:5: error: No explicit type declared for '@idx'
    a(@idx) = 1.0
      ^^^^
err01_at_subscript.f90:8:18: error: No explicit type declared for '@'
    print *, sum(a(@[1, 1]:[2, 2], 1))
                   ^
err01_at_subscript.f90:8:26: error: Must be a scalar value, but is a rank-1 array
    print *, sum(a(@[1, 1]:[2, 2], 1))
                           ^^^^^^

Bounds array in allocate (9.7.1.1):

! UNCHECKED F2023 SYNTAX: ALLOCATE with a bounds array (9.7.1.1). Rejected by gfortran 16.2 and Flang 23.
program err02_allocate_bounds
  implicit none
  real, allocatable :: a(:, :, :)
  integer :: sh(3)
  sh = [2, 3, 4]
  allocate (a(sh))
  print *, shape(a)
end program err02_allocate_bounds

gfortran 16.2:

err02_allocate_bounds.f90:7:13:

    7 |   allocate (a(sh))
      |             1
Error: Rank mismatch in array reference at (1) (1/3)

Flang 23:

error: Semantic errors in err02_allocate_bounds.f90
err02_allocate_bounds.f90:7:13: error: The number of shape specifications, when they appear, must match the rank of allocatable object
    allocate (a(sh))
              ^
err02_allocate_bounds.f90:4:24: Declared here with rank 3
    real, allocatable :: a(:, :, :)
                         ^
err02_allocate_bounds.f90:7:15: error: Must be a scalar value, but is a rank-1 array
    allocate (a(sh))
                ^^

Bounds arrays in pointer assignment (10.2.2.3):

! UNCHECKED F2023 SYNTAX: pointer bounds from arrays (10.2.2.3). Rejected by gfortran 16.2 and Flang 23.
program err03_pointer_bounds
  implicit none
  real, target :: t(24)
  real, pointer :: p(:, :, :)
  integer, parameter :: lb(3) = [0, 0, 0], ub(3) = [1, 2, 3]
  p(lb:ub) => t
  print *, shape(p)
end program err03_pointer_bounds

gfortran 16.2:

err03_pointer_bounds.f90:7:4:

    7 |   p(lb:ub) => t
      |    1
Error: Array index at (1) must be scalar

Flang 23:

error: Semantic errors in err03_pointer_bounds.f90
err03_pointer_bounds.f90:7:5: error: Must be a scalar value, but is a rank-1 array
    p(lb:ub) => t
      ^^
err03_pointer_bounds.f90:7:8: error: Must be a scalar value, but is a rank-1 array
    p(lb:ub) => t
         ^^

Bounds from an array in a declaration (8.5.8.2):

! UNCHECKED F2023 SYNTAX: bounds from an array in a declaration (8.5.8.2). Rejected by gfortran 16.2 and Flang 23.
program err04_shape_decl
  implicit none
  integer, parameter :: sh(3) = [2, 3, 4]
  real :: a(sh)
  print *, shape(a)
end program err04_shape_decl

gfortran 16.2:

err04_shape_decl.f90:5:15:

    5 |   real :: a(sh)
      |               1
Error: Explicit shaped array with nonconstant bounds at (1)
err04_shape_decl.f90:6:18:

(the remaining two lines of its output only repeat that a was not declared). Flang 23:

error: Semantic errors in err04_shape_decl.f90
err04_shape_decl.f90:5:3: error: not yet implemented: TODO: Analyze overload for ExplicitShapeBoundsSpec
    real :: a(sh)
    ^^^^^^^^^^^^^

The Flang message for the last one, "not yet implemented", is the compiler’s own statement that the construct is parsed but not supported yet. In the others the compiler reads the bounds array as a single scalar subscript.

What works today for the same jobs

Two of the jobs have a standard-conforming route. The first is a rank-one view with arbitrary lower bounds, which the Fortran 2023 lower= argument of c_f_pointer (18.2.3.3) provides: the extents and the lower bounds are rank-one integer arrays, as in the unchecked pointer assignment above. The second is an allocation whose rank is known by a select rank branch (here on an allocatable assumed-rank dummy) and whose extents are the elements of a vector.

module heatflow_kinds
  use, intrinsic :: iso_fortran_env, only: real64
  implicit none
  private
  public :: dp
  integer, parameter :: dp = real64
end module heatflow_kinds

module heatflow_reshape
  use, intrinsic :: iso_c_binding, only: c_loc, c_f_pointer
  use heatflow_kinds, only: dp
  implicit none
  private
  public :: view_3d, make_like

contains

  ! Bounds taken from integer arrays, with the Fortran 2023 c_f_pointer
  ! (its lower argument is new in F2023; shape and lower are rank-one arrays) instead of the F2023
  ! bounds-array form of pointer assignment that no compiler accepts yet.
  subroutine view_3d(flat, extents, lowers, view)
    real(dp), target, contiguous, intent(in) :: flat(:)
    integer, intent(in) :: extents(3), lowers(3)
    real(dp), pointer, contiguous, intent(out) :: view(:, :, :)
    call c_f_pointer(c_loc(flat), view, extents, lowers)
  end subroutine view_3d

  ! Allocation with a shape that is data: select rank turns the rank-one
  ! array of extents into the right number of allocate-shape-specs.
  subroutine make_like(shp, a)
    integer, intent(in) :: shp(:)
    real(dp), allocatable, intent(out) :: a(..)
    select rank (a)
    rank (1)
      allocate (a(shp(1)))
    rank (2)
      allocate (a(shp(1), shp(2)))
    rank (3)
      allocate (a(shp(1), shp(2), shp(3)))
    rank default
      error stop 'make_like: rank not supported'
    end select
  end subroutine make_like
end module heatflow_reshape

program ex09_bounds_from_arrays
  use heatflow_kinds, only: dp
  use heatflow_reshape, only: view_3d, make_like
  implicit none
  real(dp), target :: store(24)
  real(dp), pointer, contiguous :: v(:, :, :)
  real(dp), allocatable :: w(:, :)
  integer :: i
  store = [(real(i, dp), i=1, 24)]
  call view_3d(store, [2, 3, 4], [0, 0, 0], v)
  print '(a, 3i2, a, 3i2)', 'shape ', shape(v), '  lbound ', lbound(v)
  print '(a, f5.1)', 'v(0,0,0) = ', v(0, 0, 0)
  print '(a, f5.1)', 'v(1,2,3) = ', v(1, 2, 3)
  call make_like([3, 5], w)
  print '(a, 2i2)', 'shape(w) = ', shape(w)
end program ex09_bounds_from_arrays
shape  2 3 4  lbound  0 0 0
v(0,0,0) =   1.0
v(1,2,3) =  24.0
shape(w) =  3 5

The lower= argument is itself a Fortran 2023 addition, and both GNU Fortran 16.2 and Flang 23 accept it, as the output shows. The view pointer has bounds 0:1, 0:2 and 0:3, with the first element of store at v(0,0,0). For a scalar-shaped shape argument the same call can serve any fixed rank; the rank of the pointer, as before, must be known at compile time.

Choosing a technique

The decision depends on what varies and on whether the compilers in use accept the Fortran 2023 items.

flowchart TD start(["Code that must work for several kinds, ranks or types"]) --> axis{"What varies?"} axis -- rank --> pw{"Is the operation applied element by element?"} pw -- yes --> el["elemental procedure"] pw -- no --> same{"Same code for every rank?"} same -- yes --> flat["assumed-rank, contiguous, target and a c_f_pointer view"] same -- no --> sel["assumed-rank with select rank, one case per rank"] axis -- kind --> few{"Few kinds, many procedures?"} few -- yes --> inc["include-file or preprocessor template behind a generic interface"] few -- no --> gi["generic interface with one specific per kind"] axis -- type --> rt{"Is a run-time choice acceptable?"} rt -- yes --> cs["class star with select type"] rt -- no --> gi gi --> f23{"Compiler accepts typeof and classof?"} f23 -- yes --> fol["declare temporaries and results with typeof and classof"] f23 -- no --> kinds["kinds module constants"]

Fortran 2028 templates

The Fortran 2028 draft is the first to address the problem directly. The latest public working draft is J3/26-007r1 (3 March 2026). Its clause 16, "Templates, requirements, and instantiation", defines the template construct (16.1) with deferred arguments (16.4), which are the parameters of the template, the instantiate statement and inline instantiation of templated procedures (16.5), and the requirement construct and require statement (16.6, 16.7), which state what a deferred argument must provide and are the counterpart of a constraint or concept. The draft also adds generic subprograms, with the select generic rank construct (11.1.10 in the draft) and a select generic type construct, and a default kind statement. The Fortran 2023 items of this page reappear there: the rank clause and the rank intrinsic are what lets a templated procedure declare an entity whose rank depends on a deferred argument, and the draft calls such an entity implied-rank.

No compiler implements any of it, so this page has no example for it and does not describe the syntax further, because the draft may still change before publication (targeted for the second half of 2028); the clause numbers above are those of the working draft. The planned status, the proposals and a longer discussion are on What’s New and Fortran 2028 Preview. Until then, the tools of this page are the available ones.

Compiler support

The following was verified on 2026-10-18 with the examples above; "not verified" means that no compiler of that family was available for this page.

Feature GNU Fortran 16.2 LLVM Flang 23 Other compilers

Generic interfaces, elemental, class(*), include

accepted

accepted

not verified

Preprocessing of .F90 files

accepted

accepted

not verified

Assumed-rank (..) and select rank (F2018)

accepted

accepted

not verified

Allocatable assumed-rank dummy (F2018)

accepted

accepted

not verified

c_loc of an assumed-rank argument

accepted

accepted

not verified

c_f_pointer with lower= (F2023)

accepted

accepted

not verified

typeof, classof (F2023)

rejected: "Mangled derived type definition" and "Unclassifiable statement"

accepted

not verified

rank clause (F2023)

rejected: "Invalid character in name"

accepted

not verified

@ multiple subscripts and triplets (F2023)

rejected

rejected

not verified

Integer arrays for bounds in allocate, pointer assignment and declarations (F2023)

rejected

rejected, declarations "not yet implemented"

not verified

The GNU Fortran manual has no Fortran 2023 status page, so the statement for gfortran is the result of compiling the examples, and it will change with a newer release. Flang’s own table is in the Flang documentation referenced below.

Standard-version provenance

Feature Standard Notes

Generic interfaces, module procedure

Fortran 90

Resolution by type, kind and rank; the rules for distinguishing specifics were widened in Fortran 2008 and 2018.

elemental procedures

Fortran 95

impure elemental was added in Fortran 2008.

class(*) and select type

Fortran 2003

Unlimited polymorphism.

include line

Fortran 90

Not a statement (6.4).

Assumed-rank (..), select rank, the rank intrinsic

Fortran 2018

Incorporated from the Technical Specification on further interoperability of Fortran with C.

c_loc and c_sizeof of an assumed-rank argument

Fortran 2018

C840.

typeof, classof, the rank clause

Fortran 2023

7.3.2.1, 8.5.17.

@ multiple subscripts and triplets, integer arrays as bounds in declarations, allocate and pointer assignment

Fortran 2023

9.5.3.2, 8.5.8.2, 9.7.1.1, 10.2.2.3.

lower= in c_f_pointer

Fortran 2023

18.2.3.3.

Templates, requirements, generic subprograms

Fortran 2028 draft

J3/26-007r1, clause 16.

Where the books are outdated

The reference book for this section, Modern Fortran Explained: Incorporating Fortran 2023, treats the language of this page, up to the Fortran 2023 specifiers and rank facilities and an outlook on templates. The points to know when learning from it are the following.

  • The book is about the language, not the compilers. Of everything in the last chapters, only c_f_pointer with lower= and the F2018 assumed-rank facilities work on both compilers used for this page; typeof, classof and the rank clause work on Flang 23 only, and the integer-array subscripts and bounds on neither. This page supplies those facts and the diagnostics.

  • The book’s account of templates is a forward look at a possible future feature. The Fortran 2028 draft has since made them a clause of its own, with requirements and generic subprograms; they are previewed above and on the preview page.

  • Two printed passages in the book’s Fortran 2023 material are known to be wrong and should not be copied: a call to ubound that passes a section where the dimension argument belongs, and a declaration that sizes an array with the rank of a scalar, which is zero-sized because the rank of a scalar is 0 (see the remark under the rank clause). Copy from the standard instead, and check the errata file on the book’s site.

  • The book’s appendix of the authors' own recommendations advises against include files and the preprocessor; these are the authors' preferences, not obsolescent features. include is standard, and the preprocessor is a common, if nonstandard, tool for kind-generic code.

  • Older tutorials teach assumed-size arrays and one specific per rank as the only way to be rank-independent, and transfer-based tricks for the rest; assumed-rank dummies and select rank make both unnecessary for new code.

Common mistakes at a glance

Mistake Symptom Fix

Subscripting or using an assumed-rank dummy in an expression

compile error "may only be used as actual argument"

Use select rank, an inquiry function, or a c_f_pointer view.

select rank without rank default

a rank that has no case skips the whole construct, so a result set inside it keeps no value

Always add rank default, and either handle it or error stop.

Passing a noncontiguous section to a contiguous assumed-rank dummy

a temporary copy in and out; gfortran with -fcheck=all prints "An array temporary was created"

Check is_contiguous; pass whole arrays or contiguous sections.

Using c_loc on a dummy without target

compile error

Declare the dummy target.

Expecting class(*) to remove the kind specifics

one select type branch per kind remains

Use a generic interface or a template for numerical kernels.

Writing typeof, classof, the rank clause or @ in portable code today

rejected by gfortran 16.2

Use a kinds module, a dimension attribute or select rank.

rank(n) of a scalar as a size or extent

zero-sized or scalar entity, no diagnostic

Use size, or declare the array by its dimension.

Two include-file instantiations that export the same name

ambiguous reference or duplicate declaration

Rename on use (mean_sp => mean_1d) and merge under a generic.

A macro in a .f90 file

the #define line is a syntax error

Use the suffix .F90 or the compiler option -cpp.