Fortran is a strongly typed language, meaning that entities (variables, etc.) have a type that is largely known at compile-time and can be checked by the compiler. In addition to the intrinsic types that existed in FORTRAN 77 (integer, real, complex, logical, character), Fortran 90 added the concept of user-defined types or “derived types”. (FORTRAN 66 and 77 considered
DOUBLE PRECISION its own type, which led to some weird carve-outs in the language that persist to this day.)
While Fortran does have rules about conversion between numeric types (but not between logical and numeric!), you generally can’t substitute one type when the standard calls for a different type. The place where this tends to be the biggest issue is when calling procedures – the standard requires that the actual argument be “type compatible” with the dummy argument. Type compatibility also comes into play in pointer assignment and allocation of allocatable variables, but I am not going to spend additional time on those.
Fortran 2003 added the concepts of type extension and polymorphism. Type extension is where you declare a new derived type that extends (or inherits from) a parent type. For example:
type :: parent integer :: p1 end type parent type, extends(parent) :: child real :: c2 end type child type(child) :: kid
What many people don’t recognize is that an extended type contains all its parent’s components, and also a component that is the parent (parent component). In this example,
kid%p1 exists as does
kid%parent (and also
kid%parent%p1). This nesting is recursive, of course. And that brings me to a couple of other concepts not well understood.
The first is component order – this matters in only two places: structure constructors and formatted I/O when the DT edit descriptor is not used. First, each of the parent’s components appear, in order of declaration, then the components of the extended type. Here, the parent component is not included. So in the above example,
p1 would come first, then
c2. So if I wanted to initialize
kid using a structure constructor without naming the components, I could do:
kid = child(3, 4.0)
and this would assign 3 to
kid%p1 and 4.0 to
Now, you might be thinking that component order also determines storage order. Let’s take another example:
type :: mytype
integer :: a
integer :: b
end type mytype
b follow component
a in memory? The standard doesn’t specify this, unless:
- The type is given the
- The type is given the
In the case of
SEQUENCE, the order of the components is fixed but not the spacing – a compiler is free to insert padding between the components if, for example, it wants to naturally align them. Different compilers make different choices here, which means that
SEQUENCE isn’t as useful as it might at first seem, but it does let you do things such as put a variable of sequence type in
BIND is more interesting – this was introduced in Fortran 2003 as part of the C Interoperability features, and specifies that the layout in memory matches that of a C
struct with the same components (which could include padding if the companion C processor would also do so.) A restriction, of course, is that all of those components must be interoperable – not just of their type, but also things such as allocatable components are not allowed.
BIND, however, prevent you from using type extension, as well as type parameters (parameterized derived types are a topic for a different post!)
Polymorphism is where a variable can change its type. Yes, I did say earlier that Fortran is “strongly typed”, and it is – the language restricts type changes to a base type and its extensions. Fortran 2003 added polymorphism and two concepts: declared type and dynamic type. Declared type is the name of the type you gave in the variable’s declaration. Sounds simple, doesn’t it? The compiler always knows what the declared type of a variable is.
An object’s dynamic type is one that may change during program execution. To enable this you use the
CLASS keyword instead of
TYPE. (If you use
TYPE, then the dynamic type is always the same as the declared type.) When you use
CLASS, you are saying that the dynamic type may be the declared type or any of its extensions. Referring to the parent-child example above, we could declare:
class(parent), allocatable :: p
This declares p as a polymorphic, allocatable variable that can be of type parent or of type child. How does one select which type it is? In this case, since it is allocatable, we can do either:
But here’s the interesting thing. Since the declared type of
parent, we can’t reference component
c2 in regular code, since
c2 isn’t part of type parent. The solution to that is
select type (p) type is (parent) print *, "Here p is of type parent", p%p1 type is (child) print *, "Here p is of type child", p%c2 class default print *, "Here we have no idea what type p is!" end select
Inside each block in the
SELECT TYPE construct, the variable’s declared type becomes the one specified in the
In this first example, I made the variable allocatable, as that’s generally how you create one of these, but you can also have one as a pointer or as a dummy argument.
Fortran also allows you to declare that a variable is unlimited polymorphic, using the syntax
CLASS(*). Unlimited polymorphic variables can become any type or class, but you can’t do anything with them without a
Are We Compatible?
Now let’s get back to the issue of type compatibility, and in particular, its applicability in argument passing. Remember that in Fortran we talk about actual arguments – what goes in a procedure reference, and dummy arguments – what goes in a procedure definition.
Non-polymorphic entities are type compatible only with other entities of the same declared type. This means when passing arguments, the declared types must match. You can pass a
type(foo) to a
type(foo), since those are the same declared type (assuming both are using the same
type (foo)), but not to a
When the dummy argument is polymorphic, the rules are relaxed. A polymorphic dummy argument is type compatible with an actual argument if the declared type of the actual is the same as that of the dummy or is an extension of its type. So again, using our parent-child example above, the following is allowed:
type(child) :: c call sub (c) ... subroutine sub (arg) class(parent) ::arg
but the inverse (passing a
type(parent) to a
class(child) would not be allowed.
The way to think about this is that it’s OK to pass a larger type to a class that has a smaller declared type, because it doesn’t need to reference any of the extended components. But passing a smaller type to a class with larger declared type could reference components that aren’t there.
I do want to spend a moment on how the compiler handles this “under the hood”. Polymorphic variables have a data structure (“descriptor”) that the compiler uses to record information it needs, such as type identifier, type-bound procedures, extension hierarchies, etc. If you pass one polymorphic variable to another, this descriptor is passed and that, in itself, is relatively efficient. But if you pass a non-polymorphic variable to a polymorphic dummy argument (
class()), the compiler has to construct a class descriptor at the point of the call. Yes, there are optimizations that can be done, but this is still a complex operation that can generate hundreds of instructions. Try not to do this a lot if performance is a goal.
One last point that is very important! If you call a procedure that has any polymorphic dummy arguments, an explicit interface is required to be visible to the caller!
As part of polymorphism, Fortran 2003 also introduced a concept called unlimited polymorphic, declared with the syntax
CLASS(*). An unlimited polymorphic variable has no declared type and doesn’t have the same type as any other variable. However, an unlimited polymorphic variable is type compatible with anything!
Because unlimited polymorphic has no declared type, you can’t actually do anything with it unless you use
SELECT TYPE to create a block within which the variable has a known type or class.
One last thing – Fortran 2018 added
TYPE(*) or assumed type as part of the enhanced C interoperability features. This is available only for dummy arguments and is sort-of like
CLASS(*) in that it is type compatible with anything, but a major difference is that you can’t use
SELECT TYPE on an assumed type variable. It’s mainly there for interfaces to non-Fortran code and is similar to C’s
void. Like the FORTRAN 77 assumed size
(DIMENSION(*)), the type is “assumed” from the effective argument, but no information about that argument’s real type is provided. The standard gives the following restrictions on what you can do with an assumed type dummy argument (F2018 220.127.116.11p3):
- An assumed-type entity shall be a dummy data object that does not have the ALLOCATABLE, CODIMENSION, INTENT (OUT), POINTER, or VALUE attribute and is not an explicit-shape array.
- An assumed-type variable name shall not appear in a designator or expression except as an actual argument corresponding to a dummy argument that is assumed-type, or as the first argument to the intrinsic function IS_CONTIGUOUS, LBOUND, PRESENT, RANK, SHAPE, SIZE, or UBOUND, or the function C_LOC from the intrinsic module ISO_C_BINDING.
- An assumed-type actual argument that corresponds to an assumed-rank dummy argument shall be assumed-shape or assumed-rank.
Note the mention of
C_LOC there – this is the key you can use, in conjunction with
C_F_POINTER, to get at the value of an assumed-type object in Fortran code, should you want to do that.
Whew! That was one of my longer posts. As usual, if you have questions about any of this or suggestions for a future Doctor Fortran missive, add your comment below or use the contact form.