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 kid%c2.

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

Does component b follow component a in memory? The standard doesn’t specify this, unless:

  • The type is given the SEQUENCE attribute, or
  • The type is given the BIND attribute.

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 COMMON.

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.

SEQUENCE and BIND, however, prevent you from using type extension, as well as type parameters (parameterized derived types are a topic for a different post!)

Polymorphism

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:

allocate (parent::p)
or
allocate (child::p)

But here’s the interesting thing. Since the declared type of p is 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:

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 TYPE IS.

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 SELECT TYPE.

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 type(bar).

When the dummy argument is polymorphic, the rules are relaxed. A polymorphic dummy argument is type compatible with an actual argument 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!

No Limits

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 7.3.2.2p3):

  • 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.

Comments

Great post Steve. What happens to inheritance where a component is declared private? Within the module where the type is declared there is no problem but outside that module life gets more difficult especially for structure constructors?
Can this cause problems where the type extension occurs in a new module attached by use association?

And that’s a great question, Norman. As you suspect, a private component prevents you from using a structure constructor where you would need to supply a value for an inaccessible private component. The standard says:

C7102 (R756) The type name and all components of the type for which a component-spec appears shall be accessible in the scoping unit containing the structure constructor.

Typically, the use of private components is to carry internal context from the host module, and such components would get set by calls to procedures in that module. You can extend these types and access the extended components (plus accessible parent components), but you’ll have to do it with explicit component assignments and not structure constructors.

I’ll mention that one of the features planned for Fortran 202X is “Protected components”. These are accessible but read-only. The syntax and edits are not yet final – you can read the latest specification for this at https://j3-fortran.org/doc/year/20/20-106.txt

Write Your Comments

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to Doctor Fortran

Subscribe to Doctor Fortran

 

Loading