Navigation: Previous   Up   Next

9.2 Object-oriented programming

9.2.1 Type Extension

Type extension provides the first phase of object orientation: inheritance and polymorphic objects.

9.2.1.1 Extending Types [5.0]

Any derived type can be extended using the EXTENDS keyword, except for SEQUENCE types and BIND(C) types. (The latter types are “non-extensible”, as are intrinsic types, whereas all other derived types are “extensible”.) The extended type inherits all the components of the parent type and may add extra components.

For example:

  TYPE point
    REAL x,y
  END TYPE
  TYPE,EXTENDS(point) :: point_3d
    REAL z
  END TYPE
The type point_3d has x, y and z components. Additionally, it has a point component which refers to the inherited part; this “parent component” is “inheritance-associated” with the inherited components, so that the point%x component is identical to the x component et cetera.

However, when extending a type it is not required to add any new components; for example,

  TYPE,EXTENDS(point) :: newpoint
  END TYPE
defines a new type newpoint which has exactly the same components as point (plus the associated parent component). Similarly, it is no longer necessary for a type to contain any components:
  TYPE empty_type
  END TYPE
declares the extensible (but not extended) type empty_type which has no components at all.

9.2.1.2 Polymorphic Variables [5.0]

A polymorphic variable is a pointer, allocatable array or dummy argument that is declared using the CLASS keyword instead of the TYPE keyword. A CLASS(typename) variable can assume any type in the class of types consisting of TYPE(typename) and all extensions of typename.

For example:

  REAL FUNCTION bearing(a)
    CLASS(point) a
    bearing = atan2(a%y,a%x)
  END
The function bearing may be applied to a TYPE(point) object or to a TYPE(point_3d) object, or indeed to an object of any type that is an extension of TYPE(point).

9.2.1.3 Type Selection [5.0]

The SELECT TYPE construct provides both a means of testing the dynamic type of a polymorphic variable and access to the extended components of that variable.

For example:

  CLASS(t) x
  ...
  SELECT TYPE(p=>x)
  TYPE IS (t1)
    !
    ! This section is executed only if X is exactly of TYPE(t1), not an
    ! extension thereof.  P is TYPE(t1).
    !
  TYPE IS (t2)
    !
    ! This section is executed only if X is exactly of TYPE(t2), not an
    ! extension thereof.  P is TYPE(t2).
    !
  CLASS IS (t3)
    !
    ! This section is executed if X is of TYPE(t3), or of some extension
    ! thereof, and if it is not caught by a more specific case.  P is CLASS(t3).
    !
  END SELECT
Note that ‘SELECT TYPE(x)’ is short for ‘SELECT TYPE(x=>x)’.

9.2.1.4 Unlimited polymorphism [5.2]

A variable that is ‘CLASS(*)’ is an unlimited polymorphic variable. It has no type, but can assume any type including non-extensible types and intrinsic types (and kinds). Apart from allocation, deallocation and pointer assignment, to perform any operation on an unlimited polymorphic you first have to discover its type using SELECT TYPE. For example:
  CLASS(*),POINTER :: x
  CHARACTER(17),TARGET :: ch
  x => ch
  SELECT TYPE(x)
  TYPE IS (COMPLEX(KIND=KIND(0d0)))
    PRINT *,x+1
  TYPE IS (CHARACTER(LEN=*))
    PRINT *,LEN(x)
  END SELECT
Note that in the case of CHARACTER the length must be specified as ‘*’ and is automatically assumed from whatever the polymorphic is associated with.

In the case of a non-extensible (i.e. BIND(C) or SEQUENCE) type, SELECT TYPE cannot be used to discover the type; instead, an unsafe pointer assignment is allowed, for example:

  TYPE t
    SEQUENCE
    REAL x
  END TYPE
  CLASS(*),POINTER :: x
  TYPE(t),POINTER :: y
  ...
  y => x ! Unsafe - the compiler cannot tell whether X is TYPE(t).

9.2.1.5 Ad hoc type comparison [5.3]

Two new intrinsic functions are provided for comparing the dynamic types of polymorphic objects. These are

EXTENDS_TYPE_OF(A,MOLD)
SAME_TYPE_AS(A,B)

The arguments must be objects of extensible types (though they need not be polymorphic). SAME_TYPE_AS returns .TRUE. if and only if both A and B have the same dynamic type. EXTENDS_TYPE_OF returns .TRUE. if and only if the dynamic type of A is the same as, or an extension of, the dynamic type of MOLD. Note that if MOLD is an unallocated unlimited polymorphic (CLASS(*)), the result will be true regardless of the state of A.

The arguments are permitted to be unallocated or disassociated, but they are not permitted to be pointers with an undefined association status.

It is recommended that where possible these intrinsic functions be avoided, and that SELECT TYPE be used for type checking instead.

9.2.2 Typed allocation [5.1]

The ALLOCATE statement now accepts a type-spec; this can be used to specify the dynamic type (and type parameters, if any) of an allocation. The type-spec appears before the allocation list, and is separated from it by a double colon.

For example, if T is an extensible type and ET is an extension of T,

    CLASS(t),POINTER :: a(:)
    ALLOCATE(et::a(100))
allocates A to have dynamic type ET. Note that the type-spec in an ALLOCATE statement omits the TYPE keyword for derived types, similarly to the TYPE IS and CLASS IS statements.

An unlimited polymorphic object can be allocated to be any type including intrinsic types: for example

    CLASS(*),POINTER :: c,d
    ALLOCATE(DOUBLE PRECISION::c)
    READ *,n
    ALLOCATE(CHARACTER(LEN=n)::d)
allocates C to be double precision real, and D to be of type CHARACTER with length N.

Typed allocation is only useful for allocating polymorphic variables and CHARACTER variables with deferred length (LEN=:). For a non-polymorphic variable, the type-spec must specify the declared type and, if it is type CHARACTER but not deferred-length, to have the same character length. The character length must not be specifed as an asterisk (CHARACTER(LEN=*)) unless the allocate-object is a dummy argument with an asterisk character length (and vice versa).

Finally, since there is only one type-spec it must be compatible with all the items in the allocation list.

9.2.3 Sourced allocation (cloning) [5.1]

The ALLOCATE statement now accepts the SOURCE= specifier. The dynamic type and value of the allocated entity is taken from the expression in the specifier. If the derived type has type parameters (q.v.), the value for any deferred type parameter is taken from the source expression, and the values for other type parameters must agree. This is not just applicable to derived types: if the entity being allocated is type CHARACTER with deferred length (LEN=:), the character length is taken from the source expression.

Only one entity can be allocated when the SOURCE= specifier is used. Note that when allocating an array the array shape is not taken from the source expression but must be specified in the usual way. If the source expression is an array, it must have the same shape as the array being allocated.

For example,

    CLASS(*),POINTER :: a,b
    ...
    ALLOCATE(a,SOURCE=b)
The allocated variable A will be a “clone” of B, whatever the current type of B happens to be.

9.2.4 Type-bound procedures [5.1]

Type-bound procedures provide a means of packaging operations on a type with the type itself, and also for dynamic dispatch to a procedure depending on the dynamic type of a polymorphic variable.

9.2.4.1 The type-bound procedure part

The type-bound procedure part of a type definition is separated from the components by the CONTAINS statement. The default accessibility of type-bound procedures is public even if the components are private; this may be changed by using the PRIVATE statement after the CONTAINS.

9.2.4.2 Specific type-bound procedures

The syntax of a specific, non-deferred, type-bound procedure declaration is:

PROCEDURE [[,binding-attr-list]::] binding-name [=>procedure-name]

The name of the type-bound procedure is binding-name, and the name of the actual procedure which implements it is procedure-name. If the optional =>procedure-name is omitted, the actual procedure has the same name as the binding.

A type-bound procedure is invoked via an object of the type, e.g.

  CALL variable(i)%tbp(arguments)
Normally, the invoking variable is passed as an extra argument, the “passed-object dummy argument”; by default this is the first dummy argument of the actual procedure and so the first argument in the argument list becomes the second argument, etc. The passed-object dummy argument may be changed by declaring the type-bound procedure with the PASS(argument-name) attribute, in which case the variable is passed as the named argument. The PASS attribute may also be used to confirm the default (as the first argument), and the NOPASS attribute prevents passing the object as an argument at all. The passed-object dummy argument must be a polymorphic scalar variable of that type, e.g. CLASS(t) self.

When a type is extended, the new type either inherits or overrides each type-bound procedure of the old type. An overriding procedure must be compatible with the old procedure; in particular, each dummy argument must have the same type except for the passed-object dummy argument which must have the new type. A type-bound procedure that is declared to be NON_OVERRIDABLE cannot be overridden during type extension.

When a type-bound procedure is invoked, it is the dynamic type of the variable which determines which actual procedure to call.

The other attributes that a type-bound procedure may have are PUBLIC, PRIVATE, and DEFERRED (the latter only for abstract types, which are described later).

9.2.4.3 Generic type-bound procedures

A generic type-bound procedure is a set of specific type-bound procedures, in the same way that an ordinary generic procedure is a set of specific ordinary procedures. It is declared with the GENERIC statement, e.g.
  GENERIC :: generic_name => specific_name_1, specific_name_2, specific_name_3
Generic type-bound procedures may also be operators or assignment, e.g.
  GENERIC :: OPERATOR(+) => add_t_t, add_t_r, add_r_t
Such type-bound generic operators cannot have the NOPASS attribute; the dynamic type of the passed-object dummy argument determines which actual procedure is called.

When a type is extended, the new type inherits all the generic type-bound procedures without exception, and the new type may extend the generic with additional specific procedures. To override procedures in the generic, simply override the specific type-bound procedure. For example, in

  TYPE mycomplex
    ...
  CONTAINS
    PROCEDURE :: myc_plus_r => myc1_plus_r
    PROCEDURE,PASS(B) :: r_plus_myc => r_plus_myc1
    GENERIC :: OPERATOR(+) => myc_plus_r, r_plus_myc
  END TYPE
  ...
  TYPE,EXTENDS(mycomplex) :: mycomplex_2
    ...
  CONTAINS
    PROCEDURE :: myc_plus_r => myc2_plus_r
    PROCEDURE,PASS(B) :: r_plus_myc => r_plus_myc2
  END TYPE
the type mycomplex_2 inherits the generic operator ‘+’; invoking the generic (+) invokes the specific type-bound procedure, which for entities of type mycomplex_2 will invoke the overriding actual procedure (myc2_plus_r or r_plus_myc2).

9.2.5 Abstract derived types [5.1]

An extensible derived type can be declared to be ABSTRACT, e.g.
  TYPE, ABSTRACT :: mytype
An abstract type cannot be instantiated; i.e. it is not allowed to declare a non-polymorphic variable of abstract type, and a polymorphic variable of abstract type must be allocated to be a non-abstract extension of the type.

Abstract type may contain DEFERRED type-bound procedures, e.g.

  ...
  CONTAINS
    PROCEDURE(interface_name),DEFERRED :: tbpname
No binding (“=> name”) is allowed or implied by a deferred procedure binding. The interface_name must be the name of an abstract interface or a procedure with an explicit interface, and defines the interface of the deferred type-bound procedure.

When extending an abstract type, the extended type must also be abstract unless it overrides all of the deferred type-bound procedures with normal bindings.

9.2.6 Object-bound procedures [5.2]

These are procedure pointer components, and act similarly to type-bound procedures except that the binding is per-object not per-type. The syntax of a procedure pointer component declaration is:

PROCEDURE( [proc-interface] ) , proc-component-attr-spec-list :: proc-decl-list

where The POINTER attribute is required.

Note that object-bound procedures have a passed-object dummy argument just like type-bound procedures; if this is not wanted, the NOPASS attribute must be used (and this is required if the interface is implicit, i.e. when proc-interface is missing or is a type specification).

The following example demonstrates using a list of subroutines with no arguments.

  TYPE action_list
    PROCEDURE(),NOPASS,POINTER :: action => NULL()
    TYPE(action_list),POINTER :: next
  END TYPE
  TYPE(t),TARGET :: top
  TYPE(t),POINTER :: p
  EXTERNAL sub1,sub2
  top%action = sub1
  ALLOCATE(top%next)
  top%next%action = sub2
  ...
  p => top
  DO WHILE (ASSOCIATED(p))
    IF (ASSOCIATED(p%action)) CALL p%action
    p => p%next
  END DO