Fortran operator overloading and generic interfaces
last modified November 14, 2026
A type that represents a vector or a fraction should be as easy to use as an
integer. Fortran lets a type overload an intrinsic operator: after an
interface for operator(+) is written, a + b calls a
function of your own when a and b are values of the
type. The same mechanism covers assignment, comparison and named operators
such as .dot., and a generic interface gives several
procedures one common name. The compiler resolves every call from the types of
the arguments, so the notation stays natural while the work is done by ordinary
functions with explicit interfaces.
Overloading an intrinsic operator
An operator interface lists the functions that implement the operator for
particular argument types. The block interface operator(+) with
module procedure add_vec tells the compiler that two values of
type vec may be added, and the function add_vec
returns the sum. The multiplication interface names two procedures, one for
real * vec and one for vec * real, because the
operands of * are not interchangeable.
module vecmod
implicit none
private
public :: vec, operator(+), operator(-), operator(*)
type :: vec
real :: x = 0.0, y = 0.0
end type vec
interface operator(+)
module procedure add_vec
end interface
interface operator(-)
module procedure sub_vec
end interface
interface operator(*)
module procedure scale_left, scale_right
end interface
contains
pure function add_vec(a, b) result(r)
type(vec), intent(in) :: a, b
type(vec) :: r
r = vec(a%x + b%x, a%y + b%y)
end function add_vec
pure function sub_vec(a, b) result(r)
type(vec), intent(in) :: a, b
type(vec) :: r
r = vec(a%x - b%x, a%y - b%y)
end function sub_vec
pure function scale_left(k, v) result(r)
real, intent(in) :: k
type(vec), intent(in) :: v
type(vec) :: r
r = vec(k * v%x, k * v%y)
end function scale_left
pure function scale_right(v, k) result(r)
type(vec), intent(in) :: v
real, intent(in) :: k
type(vec) :: r
r = vec(v%x * k, v%y * k)
end function scale_right
end module vecmod
program w1
use vecmod
implicit none
type(vec) :: a, b, c
a = vec(1.0, 2.0)
b = vec(3.0, -1.0)
c = a + b
print *, 'a + b: ', c
c = a - b
print *, 'a - b: ', c
c = 2.0 * a
print *, '2 * a: ', c
c = b * 0.5
print *, 'b * 0.5:', c
end program w1
The sums and differences are computed component by component, and the scalar
multiplications give the expected vectors. Without the second procedure in the
multiplication interface, 2.0 * a would be a compile-time error
while a * 2.0 would work, which is a common surprise. The
interfaces are declared public in a module that is otherwise private, so the
operators are visible to the program exactly like the type itself.
$ gfortran w1.f90 -o w1 $ ./w1 a + b: 4.00000000 1.00000000 a - b: -2.00000000 3.00000000 2 * a: 2.00000000 4.00000000 b * 0.5: 1.50000000 -0.500000000
Defined assignment
Assignment can be overloaded in the same way, through
interface assignment(=). A defined assignment is used when the
types on the two sides differ from the declared type of the variable, and its
first dummy argument is the target, declared intent(out), while
the second is the source. The fraction type below accepts an integer or a real
on the right-hand side, reduces the result to lowest terms with Euclid's
algorithm, and compares two fractions by cross multiplication.
module fracmod
implicit none
private
public :: fraction, operator(*), operator(==), assignment(=), show
type :: fraction
integer :: num = 0
integer :: den = 1
end type fraction
interface assignment(=)
module procedure frac_from_int, frac_from_real
end interface
interface operator(*)
module procedure mul_fractions, mul_by_int
end interface
interface operator(==)
module procedure equal_fractions
end interface
contains
pure subroutine frac_from_int(self, n)
type(fraction), intent(out) :: self
integer, intent(in) :: n
self = fraction(n, 1)
end subroutine frac_from_int
pure subroutine frac_from_real(self, r)
type(fraction), intent(out) :: self
real, intent(in) :: r
self = reduce(nint(r * 10000.0), 10000)
end subroutine frac_from_real
pure function mul_fractions(a, b) result(r)
type(fraction), intent(in) :: a, b
type(fraction) :: r
r = reduce(a%num * b%num, a%den * b%den)
end function mul_fractions
pure function mul_by_int(a, n) result(r)
type(fraction), intent(in) :: a
integer, intent(in) :: n
type(fraction) :: r
r = reduce(a%num * n, a%den)
end function mul_by_int
pure logical function equal_fractions(a, b) result(flag)
type(fraction), intent(in) :: a, b
flag = a%num * b%den == b%num * a%den
end function equal_fractions
pure function reduce(n, d) result(r)
integer, intent(in) :: n, d
type(fraction) :: r
integer :: g
g = gcd(abs(n), abs(d))
if (g == 0) g = 1
r = fraction(n / g, d / g)
if (r%den < 0) r = fraction(-r%num, -r%den)
end function reduce
pure integer function gcd(a, b) result(g)
integer, intent(in) :: a, b
integer :: x, y, t
x = a
y = b
do while (y /= 0)
t = mod(x, y)
x = y
y = t
end do
g = x
end function gcd
subroutine show(f)
type(fraction), intent(in) :: f
print '(a, i0, a, i0)', 'fraction ', f%num, '/', f%den
end subroutine show
end module fracmod
program w2
use fracmod
implicit none
type(fraction) :: f, g, h
f = 0.25
g = 3
call show(f)
call show(g)
h = f * g
call show(h)
h = f * 8
call show(h)
print *, 'f == 0.25 as fraction:', f == fraction(1, 4)
print *, 'f == g: ', f == g
end program w2
The real 0.25 becomes 1/4 and the integer 3 becomes
3/1; multiplying them gives 3/4, and multiplying by
eight gives 2/1, already reduced. The conversion from a real
multiplies by ten thousand and reduces, so it keeps four decimal places and
nothing more; a general fraction would need a different approach. The
comparison is exact for the integer components of the type, which is why
1/4 equals a fraction built as fraction(1, 4) even
though the two values arrived by different routes.
$ gfortran w2.f90 -o w2 $ ./w2 fraction 1/4 fraction 3/1 fraction 3/4 fraction 2/1 f == 0.25 as fraction: T f == g: F
One generic name for several procedures
A generic interface is an interface block without an operator: it simply lists
procedures and gives them a common name. All the procedures must be either
functions or subroutines, and each must be distinguishable from the others by
the number, the types or the ranks of its arguments. The block below groups
three versions of sort2, one for each type, and a call is resolved
from the arguments exactly as an intrinsic procedure would be.
module sorting
implicit none
private
public :: sort2
interface sort2
module procedure sort2_int, sort2_real, sort2_char
end interface
contains
pure subroutine sort2_int(a, b)
integer, intent(inout) :: a, b
integer :: t
if (a > b) then
t = a
a = b
b = t
end if
end subroutine sort2_int
pure subroutine sort2_real(a, b)
real, intent(inout) :: a, b
real :: t
if (a > b) then
t = a
a = b
b = t
end if
end subroutine sort2_real
pure subroutine sort2_char(a, b)
character(len=*), intent(inout) :: a, b
character(len=len(a)) :: t
if (a > b) then
t = a
a = b
b = t
end if
end subroutine sort2_char
end module sorting
program w3
use sorting
implicit none
integer :: i, j
real :: x, y
character(len=10) :: s, t
i = 7
j = 3
call sort2(i, j)
print *, 'integers:', i, j
x = 2.5
y = 1.5
call sort2(x, y)
print *, 'reals: ', x, y
s = 'pear'
t = 'apple'
call sort2(s, t)
print '(a, a, a, a)', 'strings: ', trim(s), ' ', trim(t)
end program w3
The same name sorts two integers, two reals and two strings, and each call
reaches the matching procedure at compile time. The string version needs a
temporary of the same length as the arguments, which is why it declares
character(len=len(a)) :: t rather than a fixed length. A call with
a type that none of the procedures accepts is an error, and adding a new
version later means adding one line to the interface and one procedure, without
changing any existing call.
$ gfortran w3.f90 -o w3 $ ./w3 integers: 3 7 reals: 1.50000000 2.50000000 strings: apple pear
Generic functions over intrinsic and derived types
A generic interface may mix procedures that take intrinsic types with
procedures that take derived types, which is how a single mathematical name can
cover several kinds of value. The function magnitude below returns
the absolute value of a real, the modulus of a complex number, and the length
of a two-dimensional point. The three implementations share the name but not a
line of code.
module measures
implicit none
private
public :: point, magnitude
type :: point
real :: x = 0.0, y = 0.0
end type point
interface magnitude
module procedure mag_real, mag_complex, mag_point
end interface
contains
pure real function mag_real(x) result(r)
real, intent(in) :: x
r = abs(x)
end function mag_real
pure real function mag_complex(z) result(r)
complex, intent(in) :: z
r = abs(z)
end function mag_complex
pure real function mag_point(p) result(r)
type(point), intent(in) :: p
r = sqrt(p%x**2 + p%y**2)
end function mag_point
end module measures
program w4
use measures
implicit none
print *, 'real: ', magnitude(-3.5)
print *, 'complex: ', magnitude((3.0, 4.0))
print *, 'point: ', magnitude(point(6.0, 8.0))
end program w4
The real call returns 3.5, the complex number (3, 4) has modulus
5, and the point (6, 8) is at distance 10 from the origin. The
complex literal is written with parentheses around the two parts, and the point
is built with its structure constructor. Because magnitude is a
generic name rather than a type-bound one, it also works for plain values that
have no type of their own to belong to.
$ gfortran w4.f90 -o w4 $ ./w4 real: 3.50000000 complex: 5.00000000 point: 10.0000000
Named operators
Besides the intrinsic symbols, Fortran allows operators with names of your own,
written between dots. An interface for operator(.dot.) makes
a .dot. b a valid expression, and the name is as visible and as
checkable as any other operator. The example defines the dot product of two
three-dimensional vectors, which returns a real, and the cross product, which
returns a vector.
module geometry
implicit none
private
public :: vec3, operator(.dot.), operator(.cross.)
type :: vec3
real :: x = 0.0, y = 0.0, z = 0.0
end type vec3
interface operator(.dot.)
module procedure dot_product_vec
end interface
interface operator(.cross.)
module procedure cross_product_vec
end interface
contains
pure real function dot_product_vec(a, b) result(r)
type(vec3), intent(in) :: a, b
r = a%x * b%x + a%y * b%y + a%z * b%z
end function dot_product_vec
pure function cross_product_vec(a, b) result(r)
type(vec3), intent(in) :: a, b
type(vec3) :: r
r = vec3(a%y * b%z - a%z * b%y, &
a%z * b%x - a%x * b%z, &
a%x * b%y - a%y * b%x)
end function cross_product_vec
end module geometry
program w5
use geometry
implicit none
type(vec3) :: a, b, c
a = vec3(1.0, 2.0, 3.0)
b = vec3(4.0, 5.0, 6.0)
print *, 'dot: ', a .dot. b
c = a .cross. b
print *, 'cross:', c
print *, 'orthogonal:', (a .dot. c), (b .dot. c)
end program w5
The dot product of (1, 2, 3) and (4, 5, 6) is 32, and
the cross product is (-3, 6, -3). The last line checks that the
cross product is perpendicular to both inputs: each dot product with it is
zero. A named operator is only a function with two arguments, so it can be
tested and documented like one; the dotted name simply lets it appear in infix
position.
$ gfortran w5.f90 -o w5 $ ./w5 dot: 32.0000000 cross: -3.00000000 6.00000000 -3.00000000 orthogonal: 0.00000000 0.00000000
Elemental operators and array expressions
An operator procedure may be declared elemental. It then applies
to scalars and, element by element, to arrays of the type, which lets the whole
array machinery of Fortran work on values of your own. The comparison below is
written with a small tolerance, because two real numbers that were computed in
different ways are rarely equal bit for bit. With the elemental operators in
place, count, any and findloc accept
expressions of the derived type directly.
module pointmod
implicit none
private
public :: point, operator(==), operator(<)
type :: point
real :: x = 0.0, y = 0.0
end type point
interface operator(==)
module procedure equal_points
end interface
interface operator(<)
module procedure before_point
end interface
contains
elemental logical function equal_points(a, b) result(flag)
type(point), intent(in) :: a, b
flag = abs(a%x - b%x) < 1.0e-6 .and. abs(a%y - b%y) < 1.0e-6
end function equal_points
elemental logical function before_point(a, b) result(flag)
type(point), intent(in) :: a, b
flag = a%x < b%x .or. (abs(a%x - b%x) < 1.0e-6 .and. a%y < b%y)
end function before_point
end module pointmod
program w6
use pointmod
implicit none
type(point) :: a(4), key
integer :: hits
a = [point(1.0, 1.0), point(2.0, 3.0), point(1.0, 1.0), point(0.0, 5.0)]
key = point(1.0, 1.0)
hits = count(a == key)
print *, 'matches:', hits
print *, 'any match:', any(a == key)
print *, 'first before (1,1):', findloc(a < point(1.0, 1.0), .true.)
end program w6
Two of the four points equal the key, so count reports 2 and
any is true. The last line looks for the first point that is
ordered before (1, 1); only (0, 5) qualifies, and it
sits at position 4. Without the array constructor and the elemental operators
this would be a loop with a hand-written comparison, while the version above
reads like the same operation on integers.
$ gfortran w6.f90 -o w6 $ ./w6 matches: 2 any match: T first before (1,1): 4
Defined input and output
The last form of overloading concerns the read and
write statements themselves. A type can declare a
write(formatted) binding and a read(formatted)
binding, which the runtime calls whenever the type appears in formatted output
or input, including print * and write (*, '(dt)').
The subroutine receives the value, the unit, a string that names the context,
the list of edit descriptors and the usual iostat and
iomsg variables, so it can produce any text it likes and report
errors like any other input or output operation. That is how a fraction can
print itself as 3/4 instead of showing its two integer components.
Defined input and output are the most intricate part of the feature set and are rarely needed in a first program. The simpler interfaces of this article cover most of the ground: arithmetic, comparison, assignment and a readable name for a group of procedures. When a type must survive a full round trip through a text file, the same goal can also be reached with an ordinary write of the components and an ordinary read that rebuilds the value.
Next steps
The type-bound procedures article shows how an operator can also be declared inside a type, and the inheritance and polymorphism article explains how an overloaded operator can accept values of an extended type. The derived types article introduces the components used by every example here, and the formatted output article explains the edit descriptors that a defined output routine would use.
Source
Fortran 2018 standard (J3/18-007r1)
Fortran-lang: learning resources
In this article we have overloaded +, - and
* for a vector, defined assignment from integers and reals for a
fraction, given three procedures one generic name, mixed intrinsic and derived
types under magnitude, written the named operators
.dot. and .cross., and made array expressions work
with elemental comparison operators. The previous article,
Fortran inheritance and polymorphism, shows
how a type is extended and how its bindings are overridden.
Author
List all Fortran tutorials.