Interfaces, Generics and Operator Overloading
|
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 one exception is the Fortran 2023
value feature, which GNU Fortran 16.2 rejects and which is therefore labelled as checked with Flang only. Examples that are
deliberately wrong or that deliberately trigger a diagnostic are labelled as such. Interfaces are defined in clause 15 of the standard
("Procedures"): 15.4 covers the procedure interface, interface blocks, generic interfaces and procedure declaration
statements, and 15.5.5 the resolution of generic references. Defined operations are in 10.1.6 and defined assignment in 10.2.1.4.
An interface is everything a caller needs to know about a procedure: whether it is a function or a subroutine, the names, types, ranks, attributes and intents of its dummy arguments, and the type of a function result. Fortran lets the compiler check a call against that description, lets one name stand for several procedures (generics), lets the language’s own operators and assignment work on your derived types (defined operators and defined assignment), and lets procedures be passed around as data (dummy procedures and procedure pointers). All of it builds on the procedures and modules pages, and the type-bound side is continued in Type-Bound Procedures, Abstract Types and Finalization and Object Oriented Programming. Compare the same ideas in C++ (operator overloading, templates), Rust (traits and operator overloading) and C (type-generic programming); the Fortran comparison page maps these concepts across languages.
| Topic | What it gives you | Standard clause (J3/23-007r1) |
|---|---|---|
Explicit interface |
argument checking, keywords, assumed-shape, |
15.4.2 |
Interface block and interface body |
declares the interface of an external, dummy or abstract procedure |
15.4.3.2 |
|
a generic name without an interface block (Fortran 2023) |
15.4.3.3 |
Generic interface |
one name for several specifics, resolved by the arguments |
15.4.3.4.1, 15.5.5.2 |
Restrictions on generics |
when two specifics are distinguishable |
15.4.3.4.5 |
Defined operator |
|
15.4.3.4.2, 10.1.6 |
Defined assignment |
|
15.4.3.4.3, 10.2.1.4 |
Type-bound generic |
generic name, operator or assignment bound to a type |
7.5.5 |
Abstract interface and procedure pointer |
procedures as arguments, variables and components |
15.4.1, 15.4.3.6, 10.2.2.4 |
Explicit and implicit interfaces
The interface of a procedure is either explicit or implicit (15.4.2.1). It is explicit for every internal procedure, module
procedure and intrinsic procedure, for a subroutine or function within the scoping unit that defines it, and for a procedure
declared by an interface body or by a procedure declaration with an interface. Everything else, in practice an external
procedure with a bare external declaration or no declaration at all, has an implicit interface: the compiler knows
nothing about its dummy arguments, takes the types of the actual arguments as they come, and cannot check anything. Statement
functions always have an implicit interface.
Within the scope of a procedure the interface must be explicit when any of the following holds (15.4.2.2):
-
the call uses an argument keyword;
-
the procedure is used where a
pureorsimpleprocedure is required; -
a dummy argument is
allocatable,asynchronous,optional,pointer,target,valueorvolatile, is an assumed-shape array, is assumed-rank, is a coarray, is of a parameterized derived type or is polymorphic; -
the result is an array, a pointer, allocatable, or has a nonconstant type parameter;
-
the procedure is
elemental; -
the procedure has the
bindattribute.
Putting procedures in a module gives every caller that uses the module an explicit interface for free; this is why modern code has
almost no hand-written interface blocks for its own procedures, and why implicit none (type, external) (see
Variables, Declarations and Attributes) is a good default: its
external part makes a call to a procedure without an explicit interface or an external declaration an error. The next listing is
deliberately non-conforming: an external procedure with an implicit interface is called with argument keywords.
! NON-CONFORMING ON PURPOSE: an external procedure with an implicit interface
! cannot be called with argument keywords (15.4.2.2).
program implicit_keyword
implicit none
external :: smooth
real :: u(3)
u = [1.0, 2.0, 3.0]
call smooth(n=3, u=u)
end program implicit_keyword
GNU Fortran 16.2 refuses it at compile time:
err_04_implicit_keyword.f90:8:16:
8 | call smooth(n=3, u=u)
| 1
Error: Keyword argument requires explicit interface for procedure 'smooth' at (1)
Interface blocks and interface bodies
An interface block is delimited by interface and end interface and contains interface bodies (a copy of the procedure’s
header, its dummy argument declarations and its end statement) and, in a generic block, procedure statements (15.4.3.2). The
variants are named by what follows interface:
| Form | Name | Purpose |
|---|---|---|
|
specific interface block |
gives explicit interfaces to external or dummy procedures |
|
abstract interface block |
names a set of characteristics, with no procedure behind it |
|
generic interface block |
a generic name for the specifics it lists |
|
generic interface block |
a defined operator, |
|
generic interface block |
defined assignment |
|
generic interface block |
defined derived-type I/O (see Derived-Type and Asynchronous I/O) |
An interface body needs the types it uses, but it does not see the host’s entities: a name must come from its own use or import
statement (8.8), and import, none turns host association off completely. A body is only for procedures whose interface is not already
explicit: an internal procedure, a module procedure and an intrinsic procedure never need one, because their interfaces are already
explicit (15.4.3.1). The standard also lets the interface declare a non-pure procedure that is defined pure, but not the reverse.
The classic use is an external procedure written in another language or in another build target. The next program gives the BLAS dot
product ddot an explicit interface, so that the call is checked and may use keywords. ddot is a Fortran procedure, so no bind(c) is
needed; it is linked with -lblas and the actual name and meaning of each argument come from its documentation.
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_blas
use heatflow_kinds, only: dp
implicit none
private
public :: ddot
! An interface body gives an external procedure an explicit interface.
! BLAS is written in Fortran, so no bind(c) is needed; the dummy argument
! names and the intents document the contract of the routine.
interface
function ddot(n, x, incx, y, incy) result(d)
import :: dp
integer, intent(in) :: n, incx, incy
real(dp), intent(in) :: x(*), y(*)
real(dp) :: d
end function ddot
end interface
end module heatflow_blas
program interface_body_demo
use heatflow_kinds, only: dp
use heatflow_blas, only: ddot
implicit none
real(dp) :: u(4), v(4)
u = [1.0_dp, 2.0_dp, 3.0_dp, 4.0_dp]
v = [4.0_dp, 3.0_dp, 2.0_dp, 1.0_dp]
print '(a, f6.1)', 'ddot(u, v) = ', ddot(size(u), u, 1, v, 1)
print '(a, f6.1)', 'with keywords = ', ddot(x=u, y=v, n=4, incx=1, incy=1)
end program interface_body_demo
ddot(u, v) = 20.0
with keywords = 20.0
The same interface bodies are also how a C library is described (bind(c), see
Interoperability with C) and how the interface of a separate module
procedure is declared in a module so that its body can live in a submodule (see
Modules and Submodules).
Constraints worth remembering
-
An interface body must not describe a procedure that the same subprogram defines (C1501), and it must not contain a
data,format,entryor statement function (C1506). -
If
end interfacerepeats the generic specification it has to match the opening one;==and.eq.count as the same (C1502). -
A procedure must not have more than one explicit specific interface in a scoping unit; through
useassociation it may only have more than one local name (15.4.3.2). -
If the procedure does not exist in the program, the interface may be declared but the procedure must not be used.
-
An interface body of a
pureprocedure must give the intent of every dummy argument that is not a procedure, pointer orvalueargument (C1505).
Generic interfaces
A generic interface gives one name to several specific procedures. A caller writes the generic name and the compiler picks the
specific whose arguments match. Fortran’s own intrinsics (abs, sqrt, sum) work this way, and so does every overloaded
operator. A generic interface is always explicit (15.4.3.4.1).
There are two ways to write one. The generic interface block lists the specifics with module procedure (module procedures) or just
procedure (any procedure with an explicit interface, including external and dummy procedures and procedure pointers: C1507 and C1508), or holds interface
bodies. The generic statement, available from Fortran 2023 outside a type definition, does the same without an interface block and
can carry an access specifier (15.4.3.3; WG5 N2212, section 5.23). The program below uses both: rms is an interface block with
rank-1 and rank-2 real(dp) and rank-1 real(sp) specifics; mean_value is a generic statement with two specifics.
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_stats
use heatflow_kinds, only: sp, dp
implicit none
private
public :: rms
! A generic interface block: the generic name is rms, the specifics keep
! their own names (rms_1d, rms_2d, rms_1d_sp) and stay callable.
interface rms
module procedure rms_1d
module procedure rms_2d
module procedure rms_1d_sp
end interface rms
! The GENERIC statement (Fortran 2023) does the same without a block.
generic, public :: mean_value => mean_1d, mean_2d
contains
pure function rms_1d(x) result(r)
real(dp), intent(in) :: x(:)
real(dp) :: r
r = sqrt(sum(x**2)/real(size(x), dp))
end function rms_1d
pure function rms_2d(x) result(r)
real(dp), intent(in) :: x(:, :)
real(dp) :: r
r = sqrt(sum(x**2)/real(size(x), dp))
end function rms_2d
pure function rms_1d_sp(x) result(r)
real(sp), intent(in) :: x(:)
real(sp) :: r
r = sqrt(sum(x**2)/real(size(x), sp))
end function rms_1d_sp
pure function mean_1d(x) result(r)
real(dp), intent(in) :: x(:)
real(dp) :: r
r = sum(x)/real(size(x), dp)
end function mean_1d
pure function mean_2d(x) result(r)
real(dp), intent(in) :: x(:, :)
real(dp) :: r
r = sum(x)/real(size(x), dp)
end function mean_2d
end module heatflow_stats
program generic_rms_demo
use heatflow_kinds, only: sp, dp
use heatflow_stats, only: rms, mean_value
implicit none
real(dp) :: row(4), plate(3, 3)
real(sp) :: row_sp(4)
integer :: k
row = [1.0_dp, 2.0_dp, 3.0_dp, 4.0_dp]
plate = reshape([(real(k, dp), k=1, 9)], [3, 3])
row_sp = real(row, sp)
print '(a, f8.4)', 'rms of a rank-1 dp array : ', rms(row)
print '(a, f8.4)', 'rms of a rank-2 dp array : ', rms(plate)
print '(a, f8.4)', 'rms of a rank-1 sp array : ', rms(row_sp)
print '(a, f8.4)', 'mean of a rank-1 dp array: ', mean_value(row)
print '(a, f8.4)', 'mean of a rank-2 dp array: ', mean_value(plate)
end program generic_rms_demo
rms of a rank-1 dp array : 2.7386
rms of a rank-2 dp array : 5.6273
rms of a rank-1 sp array : 2.7386
mean of a rank-1 dp array: 2.5000
mean of a rank-2 dp array: 5.0000
Things to remember about the names involved:
-
The specific names stay usable:
rms_1dis still accessible inside the module and would be outside it if it were public. Put the specificsprivateand export only the generic name when the specifics are an implementation detail. -
A generic name may equal one of its own specifics, or any other accessible generic name. In the second case the two sets are merged (15.4.3.4.1), which is how a program extends a library’s generic.
-
A specific procedure may not be listed twice under the same generic identifier (C1509, C1510).
-
A generic name may equal a derived-type name, in which case every specific must be a function; this is how a type gets a second structure constructor next to the default one (the
flux_texample below). The arguments of a default structure constructor must not also be a valid generic reference (C7108, 7.5.10), so the generic takes precedence where both would fit. -
A generic name that equals the generic name of an intrinsic procedure extends the intrinsic: a reference consistent with both uses the specific from your interface, and other references still reach the intrinsic. Only if your specifics and the intrinsic are not all functions or not all subroutines is the intrinsic hidden under that name (15.4.3.4.5).
-
A function argument of a specific must have its type and type parameters declared explicitly in the specific’s interface.
Extending a generic across modules
The same generic name in a second interface block of another module adds specifics to the one accessed by use. The merged generic
is what a caller of the second module sees. This is the standard technique for adding support for a new type to a library’s generic
without editing the library.
module heatflow_describe_base
implicit none
private
public :: describe, describe_int
interface describe
module procedure describe_int
module procedure describe_logical
end interface describe
contains
subroutine describe_int(n)
integer, intent(in) :: n
print '(a, i0)', 'integer: ', n
end subroutine describe_int
subroutine describe_logical(flag)
logical, intent(in) :: flag
print '(a, l1)', 'logical: ', flag
end subroutine describe_logical
end module heatflow_describe_base
module heatflow_describe_extra
use heatflow_describe_base, only: describe
implicit none
private
public :: describe
! The same generic name in a second interface block extends the generic
! that was accessed by use association: describe now has three specifics.
interface describe
module procedure describe_text
end interface describe
contains
subroutine describe_text(text)
character(len=*), intent(in) :: text
print '(a, a)', 'text : ', text
end subroutine describe_text
end module heatflow_describe_extra
program generic_extension_demo
use heatflow_describe_extra, only: describe
use heatflow_describe_base, only: describe_int
implicit none
call describe(42)
call describe(.true.)
call describe('boundary')
! A specific procedure can still be called by its own name when it is
! accessible under that name.
call describe_int(7)
end program generic_extension_demo
integer: 42
logical: T
text : boundary
integer: 7
The only: clause on the second use shows that a specific accessible under its own name is a separate access path from the one
under the generic name: the program imports describe_int explicitly, so it can call it directly.
Distinguishing specific procedures
Two specifics under one generic name must be distinguishable, otherwise some call could match both. The compiler checks this when the generic is declared (15.4.3.4.5), long before any call. The rules use two definitions.
-
A dummy argument is TKR compatible with another if it is type compatible, has the same kind type parameters and has the same rank (or either is assumed-rank).
-
Two dummy arguments are distinguishable if one is a procedure and the other a data object; or both are data objects (or known to be functions) and neither is TKR compatible with the other; or one is
allocatableand the otherpointerwithoutintent(in); or one is a function with nonzero rank and the other is not known to be a function.
C1514 then says that two procedures with the same generic name must both be subroutines or both be functions and satisfy at least one of:
-
a non-passed-object dummy data object exists in one of them for which the number of nonoptional dummy data objects in that procedure that are TKR compatible with it exceeds the number of dummy data objects in the other, optional or not, that are not distinguishable from it;
-
one has more nonoptional dummy procedures than the other has dummy procedures (added in Fortran 2018: WG5 N2161, section 5.7);
-
both have passed-object dummy arguments and those are distinguishable;
-
one has a nonoptional dummy argument that is distinguishable by position (taking the passed-object argument out of the count) and a nonoptional dummy argument that is distinguishable by name, where the positional one is the same argument as, or earlier than, the named one.
The fourth rule is the one that guarantees a keyword call is unambiguous as well as a positional one. The rules say "kind type parameters",
so real(sp) and real(dp) are distinguishable, and "rank", so a vector and a matrix are. They say nothing of the names alone, of the
intent, or of the result type: those cannot distinguish specifics.
The program below combines the cases that do work. The two scalar specifics are distinguished by the type of their second argument
(real versus integer), so adjust(x, factor=3.0_dp) and adjust(x, passes=2) are both clear. A rank-1 specific and an elemental
specific coexist: the rank-1 one is chosen for a vector, and the elemental one for everything else.
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_adjust
use heatflow_kinds, only: dp
implicit none
private
public :: adjust
interface adjust
module procedure adjust_by_factor
module procedure adjust_by_passes
module procedure adjust_vector
module procedure adjust_elemental
end interface adjust
contains
! The second argument differs in TYPE (real versus integer), so the two
! specifics are distinguishable by position and by keyword.
pure function adjust_by_factor(t, factor) result(r)
real(dp), intent(in) :: t
real(dp), intent(in) :: factor
real(dp) :: r
r = t*factor
end function adjust_by_factor
pure function adjust_by_passes(t, passes) result(r)
real(dp), intent(in) :: t
integer, intent(in) :: passes
real(dp) :: r
r = t*0.5_dp**passes
end function adjust_by_passes
! A rank-1 specific: preferred over the elemental one for rank-1 actuals.
pure function adjust_vector(t) result(r)
real(dp), intent(in) :: t(:)
real(dp) :: r(size(t))
r = t - sum(t)/real(size(t), dp)
end function adjust_vector
! An elemental specific: used for every other rank.
elemental function adjust_elemental(t) result(r)
real(dp), intent(in) :: t
real(dp) :: r
r = max(t, 0.0_dp)
end function adjust_elemental
end module heatflow_adjust
program resolution_demo
use heatflow_kinds, only: dp
use heatflow_adjust, only: adjust
implicit none
real(dp) :: line(3), plate(2, 2)
line = [10.0_dp, 20.0_dp, 60.0_dp]
plate = reshape([-1.0_dp, 2.0_dp, -3.0_dp, 4.0_dp], [2, 2])
print '(a, f6.1)', 'factor= : ', adjust(10.0_dp, factor=3.0_dp)
print '(a, f6.1)', 'passes= : ', adjust(10.0_dp, passes=2)
print '(a, f6.1)', 'by type : ', adjust(8.0_dp, 3)
print '(a, 3f7.1)', 'rank 1 : ', adjust(line)
print '(a, 4f7.1)', 'rank 2 : ', adjust(plate)
print '(a, f6.1)', 'scalar : ', adjust(-5.0_dp)
end program resolution_demo
factor= : 30.0
passes= : 2.5
by type : 1.0
rank 1 : -20.0 -10.0 30.0
rank 2 : 0.0 2.0 0.0 4.0
scalar : 0.0
Ambiguous generics are rejected at the declaration
The next three listings are all deliberately non-conforming and show what the compiler reports for them. The first has two specifics that differ only by the name of the second dummy argument. The names are not enough: the argument types and ranks are identical, so there is no distinguishable argument by position at all.
! NON-CONFORMING ON PURPOSE: the two specifics differ only in the name of
! their second dummy argument, which does not make them distinguishable.
module bad_adjust
implicit none
interface adjust
module procedure adjust_by_factor
module procedure adjust_by_offset
end interface adjust
contains
pure function adjust_by_factor(t, factor) result(r)
real, intent(in) :: t, factor
real :: r
r = t*factor
end function adjust_by_factor
pure function adjust_by_offset(t, offset) result(r)
real, intent(in) :: t, offset
real :: r
r = t + offset
end function adjust_by_offset
end module bad_adjust
err_01_name_only.f90:10:2:
10 | pure function adjust_by_factor(t, factor) result(r)
| 1
......
15 | pure function adjust_by_offset(t, offset) result(r)
| 2
Error: Ambiguous interfaces in generic interface 'adjust' for 'adjust_by_factor' at (1) and 'adjust_by_offset' at (2)
The second makes an argument optional. The call amplify(1.0) would be a valid call of both, so they are not distinguishable. Optional
arguments are the usual source of this error in real code: a nonoptional argument has to be the distinguishing one.
! NON-CONFORMING ON PURPOSE: a call such as amplify(1.0) would match both
! specifics, because the second dummy argument of amplify_two is optional.
module bad_amplify
implicit none
interface amplify
module procedure amplify_one
module procedure amplify_two
end interface amplify
contains
function amplify_one(a) result(r)
real, intent(in) :: a
real :: r
r = a
end function amplify_one
function amplify_two(a, b) result(r)
real, intent(in) :: a
real, intent(in), optional :: b
real :: r
r = a
if (present(b)) r = a*b
end function amplify_two
end module bad_amplify
err_02_optional.f90:10:2:
10 | function amplify_one(a) result(r)
| 1
......
15 | function amplify_two(a, b) result(r)
| 2
Error: Ambiguous interfaces in generic interface 'amplify' for 'amplify_one' at (1) and 'amplify_two' at (2)
The third is distinguishable positionally (a real first against an integer first) but not by keyword, because the names t and n are
the same in both and a call blend(t=1.0, n=2) fits either. It is why the fourth rule needs a distinguishing argument by position and one by
name.
! NON-CONFORMING ON PURPOSE: the specifics are distinguishable by position
! (real first versus integer first) but not by keyword, so a call such as
! blend(t=1.0, n=2) would match both.
module bad_blend
implicit none
interface blend
module procedure blend_tn
module procedure blend_nt
end interface blend
contains
function blend_tn(t, n) result(r)
real, intent(in) :: t
integer, intent(in) :: n
real :: r
r = t*n
end function blend_tn
function blend_nt(n, t) result(r)
integer, intent(in) :: n
real, intent(in) :: t
real :: r
r = t + n
end function blend_nt
end module bad_blend
err_06_keyword_ambiguity.f90:11:2:
11 | function blend_tn(t, n) result(r)
| 1
......
17 | function blend_nt(n, t) result(r)
| 2
Error: Ambiguous interfaces in generic interface 'blend' for 'blend_tn' at (1) and 'blend_nt' at (2)
Generic resolution
A reference name(args) to a name established as generic is resolved at compile time, in this order (15.5.5.2):
defined here or accessed by use?"} B -- yes --> R1["That specific"] B -- no --> C{"Consistent with an elemental specific?"} C -- yes --> R2["That elemental specific"] C -- no --> D{"Name has the INTRINSIC attribute here
and the reference matches the intrinsic?"} D -- yes --> R3["That intrinsic procedure"] D -- no --> E{"Name generic in the host scoping unit?"} E -- yes --> F["Repeat these rules in the host"] E -- no --> G{"Name is an intrinsic procedure
and the reference matches?"} G -- yes --> R3 G -- no --> H["Compile-time error"]
Because the nonelemental search happens first, a rank-specific specific always beats an elemental one: adjust(line) above takes the
rank-1 specific, adjust(plate) falls through to the elemental one, and a scalar goes to the elemental as well. Nothing is decided at run time; there is
no virtual dispatch for a plain generic. (Type-bound generics resolve on the declared type at compile time and the specific binding on the
dynamic type at run time; that is covered in
Type-Bound Procedures, Abstract Types and Finalization.)
Generic resolution is also how the language does rank-independent procedures without macros or templates, by writing one specific per rank or an elemental one; the Generic and Rank Independent Programming page shows the other techniques, including the Fortran 2028 preview of real templates.
Defined operators
A generic interface whose name is operator(op) defines what an operator does for operands of your types. op is either an
intrinsic operator (+, -, *, /, **, //, ==, /=, <, <=, >, >=, .eq., .ne., .lt., .le., .gt., .ge., .not.,
.and., .or., .eqv., .neqv.) or a defined operator .name. made of letters between two dots. The rules (15.4.3.4.2):
-
every specific is a function with one argument (a prefix operation) or two (an infix operation); none with zero or more than two;
-
every dummy argument is a nonoptional data object with
intent(in)or, since Fortran 2023,value; -
the function result must not have assumed character length;
-
for an intrinsic operator the argument count must match the intrinsic uses of the operator, and the types, kinds or ranks must differ from those the intrinsic operator already handles, so
real + realcannot be redefined; -
+and-are both unary and binary, and one generic interface may hold both; the argument count tells them apart; -
extending one of the two forms of a relational operator extends both:
<=and.le.are the same operator, and the same goes for the other five pairs.
Precedence is the precedence of the operator’s category, whatever the function does (10.1.3): a defined unary operator binds tighter than every
intrinsic operator, and a defined binary operator binds looser than all of them, including .eqv.. That is why a user-defined .dot. needs parentheses
around anything but simple operands, and why a unary .norm. applies before an addition.
| Operator category | Precedence | Operators |
|---|---|---|
extension (defined unary) |
highest |
|
numeric |
next |
|
character |
next |
|
relational |
next |
|
logical |
next |
|
extension (defined binary) |
lowest |
|
The next module overloads the operators for a small flux_t type, the heat flux through one cell face of the heatflow grid. It
defines + (binary), - (unary and binary in the same interface), * in both operand orders, ==, a new binary operator .dot., a new unary operator
.norm., and a generic constructor named flux_t. Defined operators cannot select a component of their result, so the demo uses a show helper.
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_flux
use heatflow_kinds, only: dp
implicit none
private
public :: flux_t, operator(+), operator(-), operator(*), operator(==)
public :: operator(.dot.), operator(.norm.)
real(dp), parameter :: tolerance = 1.0e-12_dp
! Heat flux through one cell face: a two-component vector.
type :: flux_t
real(dp) :: qx = 0.0_dp
real(dp) :: qy = 0.0_dp
end type flux_t
! A generic name equal to the type name overrides nothing: the default
! structure constructor stays available and this adds a second one.
interface flux_t
module procedure flux_from_array
end interface flux_t
interface operator(+) ! binary +
module procedure flux_add
end interface operator(+)
interface operator(-) ! unary and binary -: both are generic
module procedure flux_negate
module procedure flux_subtract
end interface operator(-)
interface operator(*) ! scalar * flux and flux * scalar
module procedure real_times_flux
module procedure flux_times_real
end interface operator(*)
interface operator(==)
module procedure flux_equal
end interface operator(==)
interface operator(.dot.) ! a new binary operator
module procedure flux_dot
end interface operator(.dot.)
interface operator(.norm.) ! a new unary operator
module procedure flux_norm
end interface operator(.norm.)
contains
pure function flux_from_array(q) result(f)
real(dp), intent(in) :: q(2)
type(flux_t) :: f
f%qx = q(1)
f%qy = q(2)
end function flux_from_array
pure function flux_add(a, b) result(f)
type(flux_t), intent(in) :: a, b
type(flux_t) :: f
f = flux_t(a%qx + b%qx, a%qy + b%qy)
end function flux_add
pure function flux_negate(a) result(f)
type(flux_t), intent(in) :: a
type(flux_t) :: f
f = flux_t(-a%qx, -a%qy)
end function flux_negate
pure function flux_subtract(a, b) result(f)
type(flux_t), intent(in) :: a, b
type(flux_t) :: f
f = flux_t(a%qx - b%qx, a%qy - b%qy)
end function flux_subtract
pure function real_times_flux(s, a) result(f)
real(dp), intent(in) :: s
type(flux_t), intent(in) :: a
type(flux_t) :: f
f = flux_t(s*a%qx, s*a%qy)
end function real_times_flux
pure function flux_times_real(a, s) result(f)
type(flux_t), intent(in) :: a
real(dp), intent(in) :: s
type(flux_t) :: f
f = flux_t(s*a%qx, s*a%qy)
end function flux_times_real
pure function flux_equal(a, b) result(same)
type(flux_t), intent(in) :: a, b
logical :: same
same = abs(a%qx - b%qx) <= tolerance .and. abs(a%qy - b%qy) <= tolerance
end function flux_equal
pure function flux_dot(a, b) result(d)
type(flux_t), intent(in) :: a, b
real(dp) :: d
d = a%qx*b%qx + a%qy*b%qy
end function flux_dot
pure function flux_norm(a) result(n)
type(flux_t), intent(in) :: a
real(dp) :: n
n = sqrt(a%qx**2 + a%qy**2)
end function flux_norm
end module heatflow_flux
program flux_operators_demo
use heatflow_kinds, only: dp
use heatflow_flux
implicit none
type(flux_t) :: a, b
a = flux_t(3.0_dp, 4.0_dp) ! default structure constructor
b = flux_t([1.0_dp, 2.0_dp]) ! the generic constructor
call show('a + b ', a + b)
call show('-a ', -a)
call show('a - b ', a - b)
call show('2 * a ', 2.0_dp*a)
call show('a * 0.5 ', a*0.5_dp)
print '(a, f6.1)', 'a .dot. b = ', a .dot. b
print '(a, f6.1)', '.norm. a = ', .norm. a
print '(a, l2)', 'a == a = ', a == a
print '(a, l2)', 'a .eq. b = ', a .eq. b ! the same operator
! Precedence is that of the operator's category (10.1.3): a defined unary
! operator binds tighter than +, a defined binary operator looser than *.
print '(a, f6.1)', '.norm. a + 1 = ', .norm. a + 1.0_dp
print '(a, f6.1)', 'a .dot. 2 * b = ', a .dot. 2.0_dp*b
contains
subroutine show(label, f)
character(len=*), intent(in) :: label
type(flux_t), intent(in) :: f
print '(a, a, 2f6.1)', label, '= ', f%qx, f%qy
end subroutine show
end program flux_operators_demo
a + b = 4.0 6.0
-a = -3.0 -4.0
a - b = 2.0 2.0
2 * a = 6.0 8.0
a * 0.5 = 1.5 2.0
a .dot. b = 11.0
.norm. a = 5.0
a == a = T
a .eq. b = F
.norm. a + 1 = 6.0
a .dot. 2 * b = 22.0
Points to take from this run:
-
a .eq. banda == bcall the same function, because onlyoperator(==)was written (C1502, 10.1.6.2). -
.norm. a + 1is(.norm. a) + 1, giving6.0; if.norm.bound looser it would trya + 1, which has no definition.a .dot. 2 * bisa .dot. (2 * b)because*binds tighter than the defined binary operator; it gives22.0, the same as2 * (a .dot. b)only because the dot product is linear. -
flux_t(3.0_dp, 4.0_dp)still calls the default structure constructor, andflux_t([1.0_dp, 2.0_dp])the generic one: they are distinguished by the number and rank of the arguments. -
Exact equality on floating-point components is rarely what the caller means. The
==above compares within a tolerance, which also keeps-Wcompare-realsquiet; define the tolerance in the module, document it, and do not expect==to be transitive. -
A defined operation is evaluated as a function reference, so every rule for actual arguments applies to the operands, and the compiler may evaluate an equivalent expression instead as long as parentheses are respected (10.1.6.3).
Elemental operators and intrinsic names
An elemental specific works on operands of any rank, so one definition of + serves a scalar, a vector and a matrix of flux_t
(a scalar operand is broadcast). The same module extends the intrinsic generic abs to flux_t; the intrinsic abs of a real still works,
because a reference that matches no specific falls through to the intrinsic (15.5.5.2).
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_flux_elemental
use heatflow_kinds, only: dp
implicit none
private
public :: flux_t, operator(+), abs
type :: flux_t
real(dp) :: qx = 0.0_dp
real(dp) :: qy = 0.0_dp
end type flux_t
! One elemental specific serves scalars and arrays of every rank.
interface operator(+)
module procedure flux_add
end interface operator(+)
! Extending an intrinsic generic name: abs(flux) is now the magnitude,
! and abs of the intrinsic types keeps working.
interface abs
module procedure flux_abs
end interface abs
contains
elemental function flux_add(a, b) result(f)
type(flux_t), intent(in) :: a, b
type(flux_t) :: f
f = flux_t(a%qx + b%qx, a%qy + b%qy)
end function flux_add
elemental function flux_abs(a) result(m)
type(flux_t), intent(in) :: a
real(dp) :: m
m = sqrt(a%qx**2 + a%qy**2)
end function flux_abs
end module heatflow_flux_elemental
program elemental_operator_demo
use heatflow_kinds, only: dp
use heatflow_flux_elemental, only: flux_t, operator(+), abs
implicit none
type(flux_t) :: row(3), shifted(3), grid(2, 2)
row = [flux_t(3.0_dp, 4.0_dp), flux_t(6.0_dp, 8.0_dp), flux_t(0.0_dp, 1.0_dp)]
shifted = row + flux_t(0.0_dp, 0.0_dp) ! array + scalar
grid = reshape(row([1, 2, 3, 1]), [2, 2])
grid = grid + grid ! rank-2 + rank-2
print '(a, 3f6.1)', 'abs(row) : ', abs(row)
print '(a, 3f6.1)', 'abs(shifted) : ', abs(shifted)
print '(a, 4f6.1)', 'abs(grid+grid) : ', abs(grid)
print '(a, f6.1)', 'abs(-2.5) : ', abs(-2.5_dp)
end program elemental_operator_demo
abs(row) : 5.0 10.0 1.0
abs(shifted) : 5.0 10.0 1.0
abs(grid+grid) : 10.0 20.0 2.0 10.0
abs(-2.5) : 2.5
The value attribute in operators (Fortran 2023)
Fortran 2023 allows value instead of intent(in) for an operand of a defined operation or assignment (15.4.3.4.2; WG5 N2212, section 5.24).
LLVM Flang 23 accepts it, while GNU Fortran 16.2 still requires intent(in) and reports an error. The listing below is therefore checked
with Flang only.
module heatflow_value_ops
use, intrinsic :: iso_fortran_env, only: real64
implicit none
private
public :: flux_t, operator(*)
type :: flux_t
real(real64) :: qx = 0.0_real64
real(real64) :: qy = 0.0_real64
end type flux_t
interface operator(*)
module procedure scale_flux
end interface operator(*)
contains
! Fortran 2023: a dummy argument of a defined operation or assignment may
! have the VALUE attribute instead of INTENT(IN).
pure function scale_flux(s, f) result(g)
real(real64), value :: s
type(flux_t), value :: f
type(flux_t) :: g
g = flux_t(s*f%qx, s*f%qy)
end function scale_flux
end module heatflow_value_ops
program value_operator_demo
use, intrinsic :: iso_fortran_env, only: real64
use heatflow_value_ops, only: flux_t, operator(*)
implicit none
type(flux_t) :: g
g = 3.0_real64*flux_t(1.0_real64, 2.0_real64)
print '(a, 2f6.1)', '3 * flux = ', g%qx, g%qy
end program value_operator_demo
LLVM Flang 23 compiles and runs it:
3 * flux = 3.0 6.0
GNU Fortran 16.2 stops at the interface with this first message (the rest is a consequence of the missing module file):
11_value_in_operators.f90:20:2:
20 | pure function scale_flux(s, f) result(g)
| 1
Error: First argument of operator interface at (1) must be INTENT(IN)
11_value_in_operators.f90:31:7:
Operator rules that the compiler checks
The last operator listing is deliberately non-conforming: a dummy argument that is not intent(in), a function with three arguments used as an
operator, and a defined assignment between two default reals (which is intrinsic and cannot be redefined).
! NON-CONFORMING ON PURPOSE: three ways to break the rules for defined
! operators and defined assignment (15.4.3.4.2 and 15.4.3.4.3).
module bad_rules
implicit none
type :: flux_t
real :: qx = 0.0, qy = 0.0
end type flux_t
interface operator(+)
module procedure add_inout ! dummy argument not intent(in)
end interface operator(+)
interface operator(*)
module procedure blend_three ! operators take one or two arguments
end interface operator(*)
interface assignment(=)
module procedure real_from_real ! both sides are default real
end interface assignment(=)
contains
function add_inout(a, b) result(f)
type(flux_t), intent(inout) :: a
type(flux_t), intent(in) :: b
type(flux_t) :: f
f = flux_t(a%qx + b%qx, a%qy + b%qy)
end function add_inout
function blend_three(a, b, c) result(f)
type(flux_t), intent(in) :: a, b, c
type(flux_t) :: f
f = flux_t(a%qx + b%qx + c%qx, a%qy + b%qy + c%qy)
end function blend_three
subroutine real_from_real(x, y)
real, intent(out) :: x
real, intent(in) :: y
x = y
end subroutine real_from_real
end module bad_rules
err_03_operator_rules.f90:18:2:
18 | function add_inout(a, b) result(f)
| 1
Error: First argument of operator interface at (1) must be INTENT(IN)
err_03_operator_rules.f90:24:2:
24 | function blend_three(a, b, c) result(f)
| 1
Error: Operator interface at (1) has the wrong number of arguments
err_03_operator_rules.f90:29:3:
29 | subroutine real_from_real(x, y)
| 1~~~~~~~~~~~~~~~~~~~~~~~~
Error: Assignment operator interface at (1) must not redefine an INTRINSIC type assignment
Defined assignment
Intrinsic assignment copies every component, which is what you want for plain data, but not for a type whose components are pointers or that
must convert between representations. A generic interface named assignment(=) supplies the conversions (15.4.3.4.3). Each specific is a subroutine with exactly two
nonoptional data-object dummy arguments: the first has intent(out) or intent(inout), the second intent(in) or value. The two
arguments must differ in a way intrinsic assignment does not already cover: the second is an array of a different rank, the types do not conform
under Table 10.8 (the table of intrinsic assignment type combinations), or the first is of derived type. a = b becomes call sub(a, (b)), with the
right-hand side wrapped in parentheses so that it is an expression.
Consequences of that translation:
-
A defined assignment is an ordinary subroutine call:
intent(out)finalizes and default-initializes the left side first, and the right side cannot be modified. -
Because the right-hand side is an expression in parentheses, a second dummy argument with the
allocatableorpointerattribute can never be associated by defined assignment; call the subroutine directly in that case (NOTE 2 of 15.4.3.4.3). -
An
elementaldefined assignment applies element by element to arrays, and a scalar right side is broadcast (10.2.1.5). That makescells = 0.5_dpbelow fill a whole array. -
A defined assignment is used only for the type combinations it covers. Inside an intrinsic assignment of a derived type, a nonpointer nonallocatable component whose type has a consistent type-bound defined assignment is assigned with it, and the other components by intrinsic assignment (10.2.1.3); a module-level
interface assignment(=)is not applied to components.
Type-bound generics
The same interfaces can be bound to a type with generic in the type-bound procedure part (7.5.5). The statement is
generic :: operator(+) => add, generic :: assignment(=) => from_array, from_scalar or generic :: name => specific_1, specific_2. For a
non-name generic-spec (an operator or assignment) every specific binding must have a passed-object dummy argument (C779), because the binding is found
through the declared type of one of the operands (10.1.6.1). The passed-object dummy argument is the first one unless pass(arg) says otherwise, and nopass
removes it (7.5.4.5). C765 requires it to be a scalar, nonpointer, nonallocatable class(t) dummy argument for an extensible type t.
Differences from a module-level generic interface:
-
a type-bound generic name must not equal one of the type’s specific binding names (NOTE 1 of 7.5.5), unlike an ordinary generic name;
-
pass(b)is how a binding gets the object as the second operand, asreal_timesdoes for2.0_dp*abelow; -
the generic bindings are inherited by an extension, and a
genericstatement with the same generic-spec in the extension adds specifics to the inherited generic (7.5.5, C778); an extension overrides a specific binding by redeclaring the binding name; -
a type-bound defined operator is visible to anyone who has an object of the type, with no
useof an operator name needed.
The listing binds +, * (with pass and pass(b)) and three forms of = to flux_t: from an array, from a scalar (elemental, so an array of
flux_t can be filled in one statement), and to an array (the object is the second argument, pass(f)). A pure procedure cannot have a polymorphic
intent(out) dummy argument, so the passed-object left side is intent(inout) in the pure and elemental specifics.
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_flux_tbp
use heatflow_kinds, only: dp
implicit none
private
public :: flux_t
type :: flux_t
real(dp) :: qx = 0.0_dp
real(dp) :: qy = 0.0_dp
contains
procedure, private :: add
procedure, private :: times_real
procedure, pass(b), private :: real_times
procedure, private :: from_array
procedure, private :: from_scalar
procedure, pass(f), private :: to_array
! A type-bound generic needs a passed-object dummy argument for every
! operator or assignment binding (C779); pass(b) and pass(f) say which.
generic :: operator(+) => add
generic :: operator(*) => times_real, real_times
generic :: assignment(=) => from_array, from_scalar, to_array
end type flux_t
contains
pure function add(a, b) result(f)
class(flux_t), intent(in) :: a
type(flux_t), intent(in) :: b
type(flux_t) :: f
f = flux_t(a%qx + b%qx, a%qy + b%qy)
end function add
pure function times_real(a, s) result(f)
class(flux_t), intent(in) :: a
real(dp), intent(in) :: s
type(flux_t) :: f
f = flux_t(s*a%qx, s*a%qy)
end function times_real
pure function real_times(s, b) result(f)
real(dp), intent(in) :: s
class(flux_t), intent(in) :: b
type(flux_t) :: f
f = flux_t(s*b%qx, s*b%qy)
end function real_times
! Defined assignment: the first argument is intent(out) or intent(inout),
! the second intent(in) or value; the right-hand side is passed in
! parentheses. A pure procedure cannot have a polymorphic intent(out)
! dummy argument, so the passed-object lhs is intent(inout) here.
elemental subroutine from_scalar(lhs, rhs)
class(flux_t), intent(inout) :: lhs
real(dp), intent(in) :: rhs
lhs%qx = rhs
lhs%qy = rhs
end subroutine from_scalar
pure subroutine from_array(lhs, rhs)
class(flux_t), intent(inout) :: lhs
real(dp), intent(in) :: rhs(2)
lhs%qx = rhs(1)
lhs%qy = rhs(2)
end subroutine from_array
pure subroutine to_array(lhs, f)
real(dp), intent(out) :: lhs(2)
class(flux_t), intent(in) :: f
lhs = [f%qx, f%qy]
end subroutine to_array
end module heatflow_flux_tbp
program type_bound_demo
use heatflow_kinds, only: dp
use heatflow_flux_tbp, only: flux_t
implicit none
type(flux_t) :: a, b, cells(3)
real(dp) :: raw(2)
a = [3.0_dp, 4.0_dp] ! defined assignment, array on the right
b = 1.5_dp ! defined assignment, scalar on the right
raw = a + b ! operator(+), then defined assignment to an array
print '(a, 2f6.1)', 'raw = a + b : ', raw
raw = 2.0_dp*a ! operator(*) bound with pass(b)
print '(a, 2f6.1)', 'raw = 2 * a : ', raw
raw = a*0.5_dp ! operator(*) bound with the default pass
print '(a, 2f6.1)', 'raw = a * 0.5 : ', raw
cells = 0.5_dp ! the elemental specific fills the whole array
raw = cells(3)
print '(a, 2f6.1)', 'cells(3) : ', raw
end program type_bound_demo
raw = a + b : 4.5 5.5
raw = 2 * a : 6.0 8.0
raw = a * 0.5 : 1.5 2.0
cells(3) : 0.5 0.5
Abstract interfaces and dummy procedures
An abstract interface (abstract interface) declares characteristics without a procedure behind it (15.4.1). It is the usual way to give a pointer or dummy
procedure a full interface when no actual procedure is at hand, and it is what a deferred type-bound procedure refers to. A procedure declaration
statement (15.4.3.6) uses it: procedure(rate_fn) :: f declares f as an external or dummy procedure with that interface, procedure(rate_fn), pointer :: p
as a procedure pointer, and procedure(real(dp)) :: g as a function with an implicit interface and the given result type (the interface name can also be an
ordinary procedure, a module procedure or an intrinsic from Table 16.2, or a type specification). C1515 requires the name to be an abstract interface or a
procedure with an explicit interface.
A dummy procedure is the Fortran way to pass behaviour: the callee calls f(u) and the caller decides what f is. The characteristics of the actual
procedure must be the same as those of the dummy, except that a pure actual may be associated with a non-pure dummy (15.5.2.10). The abstract interface
below uses import :: dp so that the interface body can see the kind from the module, since an interface body does not see its host.
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_rates
use heatflow_kinds, only: dp
implicit none
private
public :: rate_fn, advance, newton_cooling, sqrt_cooling
! An abstract interface has no procedure behind it: it only names a set
! of characteristics that dummy arguments and procedure pointers can share.
abstract interface
function rate_fn(u) result(du)
import :: dp
real(dp), intent(in) :: u
real(dp) :: du
end function rate_fn
end interface
contains
! Explicit Euler with a rate supplied by the caller (a dummy procedure).
function advance(f, u0, dt, nsteps) result(u)
procedure(rate_fn) :: f
real(dp), intent(in) :: u0, dt
integer, intent(in) :: nsteps
real(dp) :: u
integer :: n
u = u0
do n = 1, nsteps
u = u + dt*f(u)
end do
end function advance
! A module procedure with the characteristics of rate_fn.
function newton_cooling(u) result(du)
real(dp), intent(in) :: u
real(dp) :: du
real(dp), parameter :: ambient = 20.0_dp, k = 0.5_dp
du = -k*(u - ambient)
end function newton_cooling
! The specific intrinsic names (DSQRT and friends) are obsolescent in
! Fortran 2023 (Annex B.3.12): wrap the generic intrinsic in a procedure
! of your own instead of passing the intrinsic itself.
function sqrt_cooling(u) result(du)
real(dp), intent(in) :: u
real(dp) :: du
du = -sqrt(u)
end function sqrt_cooling
end module heatflow_rates
program dummy_procedure_demo
use heatflow_kinds, only: dp
use heatflow_rates, only: rate_fn, advance, newton_cooling, sqrt_cooling
implicit none
procedure(rate_fn), pointer :: chosen
print '(a, f8.4)', 'module procedure : ', advance(newton_cooling, 100.0_dp, 0.1_dp, 10)
chosen => newton_cooling
print '(a, f8.4)', 'procedure pointer : ', advance(chosen, 100.0_dp, 0.1_dp, 10)
print '(a, f8.4)', 'wrapped intrinsic : ', advance(sqrt_cooling, 100.0_dp, 0.1_dp, 10)
end program dummy_procedure_demo
module procedure : 67.8990
procedure pointer : 67.8990
wrapped intrinsic : 90.2256
Details that surprise people:
-
A specific intrinsic function (
dsqrt,amax1, and the others of Table 16.2) may be an actual argument when it is named in anintrinsicstatement, but specific intrinsic names are obsolescent in Fortran 2023 (Annex B.3.12). Wrap the generic intrinsic in a procedure of your own, assqrt_coolingdoes. A generic intrinsic name used as an actual argument stands for the specific of the same name only. -
An elemental interface can describe only an external procedure (C1517), so there are no elemental dummy procedures or elemental procedure pointers; a pointer target cannot be a nonintrinsic elemental procedure (C1034, C1519), although an elemental intrinsic may be the target of a nonelemental pointer (10.2.2.4).
-
An
optionaldummy procedure is tested withpresent; a dummy procedure with thepointerattribute receives an actual that is a pointer, or a valid target withintent(in). -
The old way,
external fwith no interface, declares nothing about the arguments. It still compiles, but an implicit interface cannot be checked: avoid it in new code, useimplicit none (type, external)to catch missing declarations, and see Legacy Features and Modernization for old code that uses it.
Procedure pointers
A procedure pointer is a variable that can be associated with a procedure and called through its name. It is declared with a procedure statement that has
the pointer attribute, optionally initialized with => null() or with the name of an initial target (which implies save), and associated with a
pointer assignment p => target (10.2.2.4, 15.4.3.6). The target must have characteristics that match the pointer’s, except that it may be pure or simple
when the pointer is not, and an elemental intrinsic may be the target of a non-elemental pointer. A target cannot be a nonintrinsic elemental procedure
(C1034). The intrinsic associated(p) and associated(p, target) work on procedure pointers; null() and nullify disassociate them.
A procedure pointer component of a derived type is declared by procedure(iface), pointer, nopass :: name => null() (7.5.4.1; C761 requires pointer). nopass
means the call obj%name(args) passes args only; pass (the default) or pass(arg) means the object is passed as the first dummy argument or
the one called arg. C762 requires nopass for a component with an implicit interface or with no arguments. A function may return a procedure pointer.
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_hooks
use heatflow_kinds, only: dp
implicit none
private
public :: rate_fn, report_sub, probe_t, select_rate, constant_rate, linear_rate
type :: probe_t
character(len=12) :: label = 'probe'
! A procedure pointer component with NOPASS: no passed-object dummy.
procedure(rate_fn), pointer, nopass :: rate => null()
! With PASS the object itself is passed as the named dummy argument;
! the interface (report_sub) has self as its first argument.
procedure(report_sub), pointer, pass(self) :: report => null()
end type probe_t
! The abstract interfaces come after the type: report_sub refers to probe_t
! and probe_t refers to report_sub, and the type is imported by name.
abstract interface
function rate_fn(u) result(du)
import :: dp
real(dp), intent(in) :: u
real(dp) :: du
end function rate_fn
subroutine report_sub(self, value)
import :: dp, probe_t
class(probe_t), intent(in) :: self
real(dp), intent(in) :: value
end subroutine report_sub
end interface
contains
function constant_rate(u) result(du)
real(dp), intent(in) :: u
real(dp) :: du
du = 0.0_dp*u - 1.0_dp
end function constant_rate
function linear_rate(u) result(du)
real(dp), intent(in) :: u
real(dp) :: du
du = -0.5_dp*u
end function linear_rate
! A function can return a procedure pointer.
function select_rate(name) result(p)
character(len=*), intent(in) :: name
procedure(rate_fn), pointer :: p
select case (name)
case ('constant')
p => constant_rate
case ('linear')
p => linear_rate
case default
p => null()
end select
end function select_rate
end module heatflow_hooks
module heatflow_printers
use heatflow_kinds, only: dp
use heatflow_hooks, only: probe_t
implicit none
private
public :: print_with_label
contains
subroutine print_with_label(self, value)
class(probe_t), intent(in) :: self
real(dp), intent(in) :: value
print '(a, a, a, f7.2)', 'probe "', trim(self%label), '" reads ', value
end subroutine print_with_label
end module heatflow_printers
program procedure_pointer_demo
use heatflow_kinds, only: dp
use heatflow_hooks, only: probe_t, rate_fn, select_rate, constant_rate, linear_rate
use heatflow_printers, only: print_with_label
implicit none
type(probe_t) :: probe
procedure(rate_fn), pointer :: p
real(dp) :: u
probe%label = 'centre'
probe%rate => linear_rate ! pointer assignment to a component
probe%report => print_with_label
u = 80.0_dp
call probe%report(probe%rate(u)) ! probe is passed as self; rate gets no object
print '(a, l2)', 'rate points to linear_rate :', associated(probe%rate, linear_rate)
print '(a, l2)', 'rate points to constant_rate:', associated(probe%rate, constant_rate)
p => select_rate('constant')
print '(a, f7.2)', 'selected constant rate :', p(u)
p => select_rate('unknown')
print '(a, l2)', 'unknown name gives a pointer:', associated(p)
probe%rate => null()
print '(a, l2)', 'after => null(), associated :', associated(probe%rate)
end program procedure_pointer_demo
probe "centre" reads -40.00
rate points to linear_rate : T
rate points to constant_rate: F
selected constant rate : -1.00
unknown name gives a pointer: F
after => null(), associated : F
Notes:
-
The abstract interface
report_subnames the type that contains the procedure pointer that uses it. The two refer to each other, so the type is defined first and the abstract interface follows it withimport :: probe_t, while the component names the interface before it is declared. The standard asks for an earlier declaration only when the interface name comes from aproceduredeclaration statement (C1515), and both compilers accept this order. -
Invoking a disassociated procedure pointer is not allowed and is not diagnosed by
-fcheck=all(the last listing of the next subsection shows what happens). Test withassociatedbefore the call whennull()is a legitimate state. -
A procedure pointer component is not a type-bound procedure: it is data, it is not inherited the way a binding is, it can differ from one object to the next, and assigning an object copies the pointer rather than the procedure. Use a
deferredbinding for polymorphism that all objects of a type share, and a procedure pointer component for per-object callbacks. -
A table of procedures is an array of a derived type that has one procedure pointer component, because there are no arrays of procedure pointers.
Internal procedures as dummy arguments and targets
Since Fortran 2008 an internal procedure may be an actual argument or the target of a procedure pointer (WG5 N1891, section 10.4), which lets it capture data from
its host by host association. The host instance that was current when the association was made is the one the procedure sees (15.6.2.4), so the pointer
must not be used after the host returns: that is undefined behaviour and there is no run-time check for it. The program below scans a list of values of
a stiffness k; decay reads k from the host scan_stiffness.
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_integrate
use heatflow_kinds, only: dp
implicit none
private
public :: rate_fn, advance
abstract interface
function rate_fn(u) result(du)
import :: dp
real(dp), intent(in) :: u
real(dp) :: du
end function rate_fn
end interface
contains
function advance(f, u0, dt, nsteps) result(u)
procedure(rate_fn) :: f
real(dp), intent(in) :: u0, dt
integer, intent(in) :: nsteps
real(dp) :: u
integer :: n
u = u0
do n = 1, nsteps
u = u + dt*f(u)
end do
end function advance
end module heatflow_integrate
subroutine scan_stiffness(stiffness)
use heatflow_kinds, only: dp
use heatflow_integrate, only: rate_fn, advance
implicit none
real(dp), intent(in) :: stiffness(:)
procedure(rate_fn), pointer :: p
real(dp) :: k
integer :: i
p => decay ! an internal procedure as a pointer target
do i = 1, size(stiffness)
k = stiffness(i) ! decay sees the current value of k
print '(a, f4.1, a, f8.4)', 'k = ', k, ' -> ', advance(p, 100.0_dp, 0.05_dp, 10)
end do
! p must not be used after this subroutine returns: its host instance
! (and so k) ceases to exist (15.6.2.4).
contains
function decay(u) result(du)
real(dp), intent(in) :: u
real(dp) :: du
du = -k*u
end function decay
end subroutine scan_stiffness
program internal_target_demo
use heatflow_kinds, only: dp
implicit none
interface
subroutine scan_stiffness(stiffness)
import :: dp
real(dp), intent(in) :: stiffness(:)
end subroutine scan_stiffness
end interface
call scan_stiffness([0.5_dp, 1.0_dp, 2.0_dp])
end program internal_target_demo
It was built with -ftrampoline-impl=heap added to the usual flags; the output is the same as without:
k = 0.5 -> 77.6330
k = 1.0 -> 59.8737
k = 2.0 -> 34.8678
GNU Fortran implements an internal procedure used this way with a trampoline, a small piece of code on the stack. On Linux the linker then warns about an executable stack unless the trampoline is allocated on the heap. Without the extra option the same program links with this warning (Flang prints a similar one):
/usr/bin/ld: warning: /tmp/cczOFf0O.o: requires executable stack (because the .note.GNU-stack section is executable)
link-done
-ftrampoline-impl=heap (a GCC option, available on x86_64 and aarch64 Linux and x86_64 Darwin, see the GCC code-generation options) removes the executable-stack requirement at the price
of a heap allocation. Passing a module procedure instead of an internal one avoids the trampoline completely, and is the better design whenever the data can be passed as an argument.
The last listing is deliberately erroneous: it calls a disassociated procedure pointer. The standard puts no requirement on the result; GNU Fortran 16.2 (with -fcheck=all)
and Flang 23 both end in a segmentation fault, and -fcheck=all does not catch it.
! NON-CONFORMING ON PURPOSE: the procedure pointer is disassociated when it
! is referenced. The standard puts no requirement on what happens next.
program null_procedure_pointer
implicit none
abstract interface
function rate_fn(u) result(du)
real, intent(in) :: u
real :: du
end function rate_fn
end interface
procedure(rate_fn), pointer :: p => null()
real :: u
u = 80.0
print '(a, l2)', 'associated: ', associated(p)
print '(a, f8.2)', 'p(u) : ', p(u)
end program null_procedure_pointer
associated: F
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
Backtrace for this error:
#0 0xffff9096196f in ???
#1 0xffff909605df in ???
#2 0xffff90b3b7ff in ???
#3 0x0 in ???
Segmentation fault
Compiler support and diagnostics
| Feature | GNU Fortran 16.2 | LLVM Flang 23 | Notes |
|---|---|---|---|
Generic interface block, |
yes |
yes |
all examples above |
|
yes |
yes |
example |
Defined operators, assignment, generic constructor named like the type |
yes |
yes |
examples |
|
no, error |
yes |
example |
Procedure pointers, pointer components, |
yes |
yes |
example |
Internal procedure as actual argument or target |
yes, with trampoline |
yes, with trampoline |
executable-stack linker warning; |
Ambiguous generic |
compile error, "Ambiguous interfaces in generic interface" |
compile error |
errors above are from GNU Fortran |
Keyword call to an implicit interface |
compile error |
compile error |
err_04 |
Call of a disassociated procedure pointer |
SIGSEGV |
SIGSEGV |
no run-time check |
Other vendors (Intel ifx, NAG, Cray) were not available for this page and are not described here; check the compiler’s own table of Fortran 2023 features before depending on the two Fortran 2023 items above. The compiler flags that make these mistakes visible are described in Compilers and Compiler Options. For a source-level style guide see Best Practices and Style.
Common mistakes
| Mistake | What happens and what to do |
|---|---|
Specifics that differ only by dummy argument name, intent or result type |
ambiguous generic; add a type, kind or rank difference to a nonoptional argument |
Making the distinguishing argument |
ambiguous generic; keep it nonoptional, or give the specifics different required arguments |
Positional distinction without keyword distinction (swapped argument order of the same types) |
rejected by the fourth rule of C1514; rename or retype one argument |
Operator function with an argument that is not |
error; the operands are expressions and cannot be changed |
Defined assignment between two default reals or two default integers |
error; intrinsic assignment cannot be redefined |
|
the defined binary operator is the loosest; parenthesize |
Selecting a component of an operator result, |
syntax error; assign to a variable or call a helper first |
Comparing real components with |
false negatives from rounding; compare with a documented tolerance |
Calling an external procedure with keywords and an implicit interface |
compile error; give it an interface body or put it in a module |
Passing an internal procedure out of its host, or keeping a pointer to it after the host returns |
undefined behaviour; no run-time check |
Calling a procedure pointer that is not associated |
segmentation fault; test |
Assuming a procedure pointer or dummy procedure with an implicit interface is checked |
it is not; use an abstract interface |
Listing a specific procedure twice under one generic name |
error (C1509, C1510) |
Using the specific intrinsic name ( |
obsolescent in Fortran 2023; wrap the generic intrinsic in your own procedure |
Where the books are outdated
The reference book for this section, Modern Fortran Explained: Incorporating Fortran 2023, covers the 2023 standard, including the generic statement
outside a type definition, and the sections consulted for this page (generic interfaces, defined operators, defined assignment, abstract interfaces and procedure
pointers) agree with the standard. The points to know are the following.
-
The book’s appendix of the authors' own recommendations advises against
.eq.-style operators and against implicit interfaces. These are the authors' preferences and not deprecations in the standard:.eq.and==are the same operator (10.1.6.2), and an implicit interface is still valid, thoughimplicit none (external)can be used to rule it out. -
The examples give operand dummy arguments
intent(in), which was the only choice for a long time; Fortran 2023 also permitsvalue(15.4.3.4.2), but GNU Fortran 16.2 does not yet accept it, as shown above. -
The book is about the language, not about compilers. The internal-procedure trampoline and its executable-stack warning, the
-ftrampoline-impl=heapoption, the missing run-time check for a disassociated procedure pointer, and the compilers' support for the Fortran 2023 items above are practical matters of GNU Fortran and Flang that this page supplies. -
Older tutorials, from the Fortran 90 and 95 era, show only interface blocks with
module procedure, do not allow an internal procedure as an actual argument or pointer target (allowed since Fortran 2008), and state that two specifics cannot be told apart bypointerversusallocatableor by a procedure versus a data object (both added in Fortran 2008) or by the number of dummy procedures (added in Fortran 2018). They also pass specific intrinsic names such asdsqrtas actual arguments, which Fortran 2023 declares obsolescent. -
The MATLAB book’s Fortran examples declare the gateway routine’s arguments with old-style type statements instead of interfaces; the MATLAB page uses explicit interfaces throughout.
References
-
J3/23-007r1: Fortran 2023 final draft (7.5.4.5, 7.5.5, 8.8, 10.1.3, 10.1.6, 10.2.1.4, 10.2.1.5, 10.2.2.4, 15.4, 15.5.2.10, 15.5.5.2, 15.6.2.4, Annex B.3.12 and C.11.6)
-
WG5 N2212: The new features of Fortran 2023 (the generic statement outside a type, the value attribute in defined operations)
-
WG5 N2161: The new features of Fortran 2018 (rules for generic procedures)
-
WG5 N1891: The new features of Fortran 2008 (internal procedure as an actual argument or pointer target, generic resolution by pointer or allocatable)
-
GNU Fortran: Options to request or suppress errors and warnings
-
GCC: Options for code generation conventions (
-ftrampoline-impl)