fortranderived-types

How to support multiple versions of the 'same' derived type in Fortran?


EDIT to provide more details:

1) The code that provides the libraries cannot be (easily) changed so profile_v1_type and profile_v2_type should be assumed to be immutable.

I have implemented @francescalus suggestion and it works for my small test case, but I was insufficiently clear about the problem, I think. The reason being that I can only modify my code not the code/types coming from the library. The problem will be that both will have t in the profile_type that gets imported which clash with the parent type.

But I am going to implement something where I replicate the contents of the derived type that I want and then use pointers and type-bound procedures to point to the components of the profile_type version that I want to use. It's not as clean as I wanted it to be but it's much better than I have now.


I am supporting a code that interfaces with another that has 2 versions - the two versions are very similar in interface and although the inputs and outputs are identical in property, they are obviously different derived types (they come from different libraries and differ slightly in the variables contained within. Most variable names inside these types are the same though crucially).

It is (apparently) necessary to support both at runtime, otherwise I would preprocess this all at compile time.

At the moment I have lazily copied and pasted the same code for each version (and all of the versions of derived types it uses) into separate subroutines (*_v1.f90, *_v2.f90).

This is annoying and not very maintainable.

What I'd like to be able to do is use some kind of pointer that doesn't care about what it's pointing to (or rather gets its type information from what it points to and is smart enough to know what's inside).

As I said above, the names are mostly the same, e.g. (t, for temperature, say)

From v1 of library:

TYPE profile_v1_type
    REAL :: t
! loads of other stuff
END TYPE profile_v1_type

From v2 of library:

TYPE profile_v2_type
    REAL :: t
! loads of other stuff, more than the first version
END TYPE profile_v2_type

In my code:

TYPE profile_container_type
    TYPE(profile_v1_type) :: profile_v1
    TYPE(profile_v2_type) :: profile_v2
! other arrays that are common inputs to both
END TYPE

! It's actually USE'd then allocated and initialised elsewhere, but I hope you get the idea

!USE profile_container_mod, ONLY : profile_container

TYPE(profile_container_type), TARGET :: profile_container
TYPE(*) :: p

REAL :: t1

!Version determined by a namelist

IF (Version == 1) THEN
    p => profile_container % profile_v1
ELSE IF (Version == 2) THEN
    p => profile_container % profile_v2
ENDIF

t1 = p % t + 1
.
.
.

ifort 19 gives these (expected) errors:

test.f90(24): error #8776: An assumed type object must be a DUMMY argument.   [P]
TYPE(*), POINTER :: p
--------------------^
test.f90(24): error #8772: An assumed type object must not have the ALLOCATABLE, CODIMENSION, POINTER, INTENT(OUT) or VALUE attribute.   [P]
TYPE(*), POINTER :: p
--------------------^
test.f90(39): error #6460: This is not a field name that is defined in the encompassing structure.   [T]
t1 = p % t + 1
---------^
compilation aborted for test.f90 (code 1)

replace TYPE(*) with CLASS(*) gives the (still expected):

test2.f90(39): error #6460: This is not a field name that is defined in the encompassing structure.   [T]

t1 = p % t + 1 ! or some clever function...
---------^
compilation aborted for test2.f90 (code 1)

This is fixed by SELECTing the type that you want to handle, but my point is that I want to do the same thing for either the v1 and v2 code (it will never be both). And I want to do it many times, not in this routine but in about a dozen routines.

I am open to using C pointers if the responder is able to provide a simple example to follow. I have tried (not recently) to solve this problem using C interoperability, but obviously without success!


Solution

  • Unlimited polymorphic entities are not the correct approach here.

    Instead, we can define a base type which incorporates all of the common data and processing for the various other types. Here, let's call this base type profile_base_type:

    type profile_base_type
      real t
    end type
    

    The other two specific profiles can extend this base:

    type, extends(profile_base_type) :: profile_v1_type
      ! v1 specific parts
    end type
    
    type, extends(profile_base_type) :: profile_v2_type
      ! v2 specific parts
    end type
    

    Then we can declare a polymorphic pointer of the base type

    class(profile_base_type), pointer :: p
    

    which can point to targets of either of the extending types:

    p => profile_container%profile_v1
    p => profile_container%profile_v2
    

    Now, we can access the components of p which are in the type profile_base_type

    t1 = p%t + 1
    

    without having to use a select type construct.

    Naturally, those specific aspects of the extending types cannot be accessed in this way but there are other considerations for that.