Interfaces and overloading
Last updated on 2026-07-26 | Edit this page
Overview
Questions
- How are interfaces used for generic programming?
Objectives
- Understand how to define generic interfaces, overload operators and define new names.
- Understand generic interface resolution and potential pitfalls.
The interface block has three different forms in
Fortran: it may define an abstract interface, a
generic interface, or a specific interface.
In this section, we will look at generic interfaces. Generic is the term which describes overloading in Fortran.
Generic interfaces
The formal description of an interface block in the generic context is
The generic-spec has a number of different possible
forms:
-
assignment (=)for assignment - a generic procedure name
-
operator (defined-operator)for an operator - a derived type i/o operation
We have seen an example of this type of interface in the context of overloading assignment, e.g.,
There are one or more interface-specification entries
which can take different forms. As we will usually expect to place
interface definitions in a module along with the relevant procedure
definitions, the interface-specification will often involve
one or more module procedure statements of the form
The module in module procedure in the
interface block is optional: we could use external procedures. However,
it may be most natural to place the interface in the relevant module.
The procedure interface must be available in either form.
Generic assignment
We have seen an example of overloading assignment in the previous section:
In this context, the procedure must be a subroutine (not a function)
with exactly two non-optional arguments. To prevent ambiguity, the first
argument must correspond to the left-hand side of the assignment and
have intent(inout) or intent(out), while the
second argument must represent the right-hand side of the assignment and
have intent(in).
Redefining assignment between intrinsic types is not allowed,
including redefining conforming array assignments. It is possible to
define assignment between an intrinsic type and a derived type. For
example, using the my_array_t again from the previous
section:
FORTRAN
subroutine my_assignment_from_int(a, ival)
type (my_array_t), intent(inout) :: a
integer, intent(in) :: ival
a%values(:) = ival
end subroutine my_assignment_from_int
In this way, one can have a number of different procedures which overload the assignment: they are generic.
If the right-hand side is an expression, one can consider the assignment as being replaced by a call to the subroutine, with the second argument being the expression enclosed in parentheses (the right-hand side).
If an elemental subroutine is provided as a generic interface, both scalar and array assignments can be performed. Particular combinations of ranks may be specified (in which case intrinsic assignment remains available for other combinations).
Generic procedures
A generic procedure allows one to associate a generic name with one or more specific procedures.
FORTRAN
interface generic-name
module procedure :: specific-prcedure-list
end interface generic-name
The specific-procedure-list can be a comma-separated
list of different implementations, or one may add new implementation on
different lines. For example,
FORTRAN
interface my_generic_name
module procedure :: my_real32_implementation
module procedure :: my_real64_implementation
end interface my_generic_name
We are required to implement subroutines which are distinguishable by their arguments.
Generic procedure kinds
A procedure-list must consist of either all subroutines
or all functions: there cannot be a mix of subroutines and
functions.
Distinguishable procedures
In order that the compiler can identify unambiguously the appropriate specific implementation based on the actual arguments at the point of invocation, the signatures of a generic procedure must be distinguishable.
Distinguishing dummy arguments
Individual pairs of dummy arguments are distinguishable:
- if both are data objects and have different types, kind type parameters or ranks;
- if one is allocatable and the other is a pointer;
- if one is a procedure and the other is a data object.
Together, the set of non-optional arguments in each case must be distinguishable. Note that the order may not be counted upon if the dummy arguments in the two procedures have the same name, as one can always call using keywords at the point of invocation, e.g.,
The presence or absence of a pointer attribute in the dummy argument is not enough to disambiguate a call; a pointer actual argument can be associated with a dummy argument which is a non-pointer data object.
Example (10 minutes)
Constructor overloading
Looking again at our my_array_t example, check that you
can overload the default structure constructor my_array_t()
using the existing function my_array_allocate(). A new
version of the module and program are available in the
exercises/05-interfaces-overloading directory (or you can
keep your existing one).
Constructor overloading (continued)
Add a new overloaded version to the same generic name which allows us to allocate a new array of a given size, but with the values initialised uniformly by a scalar integer supplied as a second argument to the new implementation. Check this works as expected.
Extend the generic interface to also resolve to
my_array_allocate_set when passed two arguments.
FORTRAN
interface my_array_t
module procedure my_array_allocate
module procedure my_array_allocate_set
end interface my_array_t
contains
function my_array_allocate_set(nlen, val) result(a)
integer, intent(in) :: nlen
real, intent(in) :: val
type(my_array_t) :: a
a%nlen = nlen
allocate(a%values(nlen))
a%values(:) = val
end function my_array_allocate_set
Calling a = my_array_t(4, 5.0) should now (re)allocate
a with four entries, each set to 5.0.
Overloading operators
It is possible to overload an intrinsic operator (such as
+), or define a new name for an operation. The intrinsic
operators include arithmetic operators +, -, *, / etc,
logical operators including .not., .or., and relational
operators including ==, <, <=. The choice of name or
symbol may depend on how meaning is ascribed to the operation.
To do so, one must provide a function (not a subroutine) taking
exactly one argument for unary operations such as negation, or exactly
two arguments for binary operations such as multiplication. The
arguments must be intent(in) in both cases. The arguments
cannot be optional.
One cannot overload operations for intrinsic types. They are only relevant for combinations of derived types, or derived types and intrinsic types.
For example, if we had two types
FORTRAN
type (date_time_t) :: now ! a date and time
type (time_interval_t) :: dt ! interval in seconds
one might wish to overload the meaning of + to allow an
interval to be added to a date. This could be done as follows, assume we
have a module which makes available both types:
FORTRAN
interface operator (+)
module procedure :: add_interval_to_date_time
end interface operator (+)
We would then have to supply the function
FORTRAN
function add_interval_to_date_time(date, dt) result(newdate)
type (date_time_t), intent(in) :: date
type (time_interval_t), intent(in) :: dt
type (date_time_t) :: newdate
! ... details of implementation ...
end function add_interval_to_date_time
An expression of the form
would then be replaced by the result of the function, which in this
case is of type date_time_t.
The order is significant here. The left-hand operand of the operation
corresponds to the first dummy argument, and the right-hand operand
corresponds to the second dummy argument. So one could not have
dt + now.
One could, equivalently, invoke the function directly:
Caution
This is a (deliberately) somewhat contentious example. It raises a number of general concerns about the approach:
- If operations involve a single type, the operands are interchangeable, and a limited number of functions is required.
- If operations involving even one defined type and one intrinsic type are required, then the number of possible operations can quickly become quite large (without asking the question whether specific operations have meaning).
So it can be quite difficult to achieve a complete, consistent, and transparent set of operations for any new type.
Using a non-intrinsic name
We could equally define a new name
FORTRAN
interface operator(.add.)
module procedure add_interval_to_date
end interface operator (.add.)
in which case we would write
A new name must be of this form (having . at start and
finish) and must not be either .true. or
.false..
If one limits the choice to the intrinsic operator names, the
precedence of the different operations is the same, e.g., the precedence
of * is always greater than +. If using new
names, precedence in complex expressions may need to be established via
parentheses.
Exercise (20 minutes)
Dot and cross products
Write a module which defines a type to hold a 3-vector
(u_1, u_2, u_3) where the components are integers. Define
an operator .x. which computes the cross product of two
vectors, being
\(\mathbf{u} \times \mathbf{v} = (u_2 v_3 - u_3 v_2, u_3 v_1 - u_1 v_3, u_1 v_2 - u_2 v_1).\)
This should also allow a vector triple product such as
\(\mathbf{u} \times \left( \mathbf{v} \times \mathbf{w} \right)\)
noting the order of the cross products is important.
One could also define a scalar product .dot., being
\(\mathbf{u} \cdot \mathbf{v} = u_1 v_1 + u_2 v_2 + u_3 v_3.\)
Does a scalar triple product
\(\mathbf{u} \cdot \mathbf{v} \times \mathbf{w}\)
work correctly without parentheses?
A template is provided
where the example program has a number of suggestions to check the results.
FORTRAN
interface operator(.dot.)
module procedure dot_prod
end interface operator(.dot.)
interface operator (.x.)
module procedure cross_prod
end interface operator (.x.)
contains
module function dot_prod(a, b) result(d)
type(my_vector_t), intent(in) :: a, b
integer :: d
d = a%x * b%x + a%y * b%y + a%z * b%z
end function dot_prod
module function cross_prod(a, b) result(z)
type(my_vector_t), intent(in) :: a, b
type(my_vector_t) :: z
z%x = a%y * b%z - b%y * a%z
z%y = -(a%x * b%z - b%x * a%z)
z%z = a%x * b%y - b%x * a%y
end function cross_prod
The scalar triple product requires parentheses as evaluating the dot product would return a scalar as the first argument to the cross product (incorrect).
- Interfaces allow us to write generic, high-level code.
- Designing a generic interface requires care, particularly for generic operators.