Container to Handle Multiple Intrinsic Types?

Container to Handle Multiple Intrinsic Types?

Greetings!

I need a container that holds multiple intrinsic types (real, character, etc.).  Specfically, I'm looking to store an array of intrinsic type, along with some metadata, into a derived type.  The intrinsic type array needs to be resizable and accessed from the container as I loop through the contents of a file that contains a mix of real, integer and character "values".

Something like:

type charvar

    character(len=6) :: mnemonic

    character(len=255) :: description

    character(len=40) :: units

    character(len=255), dimension(:), allocatable :: values

  contains

    procedure :: setMNEM

    procedure :: getMNEM

    ...

    procedure :: addValue

    procedure :: getValues

end type charvar

type realvar

    character(len=6) :: mnemonic

    character(len=255) :: description

    character(len=40) :: units

    real, dimension(:), allocatable :: values

  contains

    procedure :: setMNEM

    procedure :: getMNEM

    ...

    procedure :: addValue

    procedure :: getValues

end type realvar

Because I have a file that contains a mix of real and character "values", I need to store them in a container and then access the container at some later point.  

What is the best way to approach this?

I have tried creating a superclass of 'variable', like:

type variable

    character(len=6) :: mnemonic

    character(len=255) :: description

    character(len=40) :: units

  contains

    procedure :: setMNEM

    procedure :: getMNEM

    ...

    procedure(add_Value),deferred :: addValue

    procedure(get_Value),deferred :: getValues

end type variable

abstract interface

    subroutine add_Value(this, val)
        import variable
        class(variable) :: this
        class(*),intent(in) :: val
    end subroutine add_Value

    subroutine get_Value(this,val)
        import variable
        class(variable) :: this
        class(*), allocatable, dimension(:),intent(inout) :: val
    end subroutine get_Value
end interface

and then charvar and realvar extends variable and implements the deferred procedures, like this:

subroutine addValue(this, val)
    class(realvar) :: this
    real, allocatable, dimension(:) :: realarr
    integer :: s
    !real, intent(in) :: val
    class(*), intent(in) :: val

    select type (val)
    type is (real)

        if (allocated(this%values) .eqv. .false.) then
            allocate(this%values(1))
            this%values(1) = val
        else
            allocate(realarr(size(this%values)+1))
            realarr(1:size(this%values)) = this%values
            call move_alloc(from=realarr,to=this%values)
            s = size(this%values)
            this%values(s) = val
        endif

    end select

    end subroutine addValue

subroutine getValues(this, val)
    class(realvar) :: this
    class(*), allocatable, dimension(:),intent(inout) :: val
    if (allocated(this%values) .eqv. .true.) then
        select type (val)
        type is (real)
            allocate(val(size(this%values)),source=this%values)
        end select
    else
        stop 'No Values'
    endif
end subroutine getValues

Then I create a container that holds the superclass

type :: container
    class(variable), allocatable :: value
end type container

and in my main program make that congtainer allocatable

type(container), dimension(:), allocatable :: message_array

and then attempt to access (add/get) the values as such:

type(realvar) :: rv1
type(charvar) :: cv1

real, dimension(:), allocatable :: real_vals
character(len=255), dimension(:), allocatable :: char_vals

allocate(message_array(2))
rv1 = realvar('ER45GW')
cv1 = charvar('L0MR67')
allocate(message_array(1)%contents, source=rv1)
allocate(message_array(2)%contents, source=cv1)

call message_array(1)%contents%addValue(-56.579)

call message_array(1)%contents%addValue(219.08)

call message_array(2)%contents%addValue('Yadda yadda yadda')

call message_array(2)%contents%addValue('Blah, blah, blah')

call message_array(1)%contents%getValues(real_vals)

call message_array(2)%contents%getValues(char_vals)

I have not yet had success with this (running 12.1.5), particularly getting the values back out. 

SO BOTTOM LINE: is this best way to go about storing multiple instrinsic data types, or should I be barking up another tree?  I've attemprted linked list but still haven't got it quite figured out either.  If there is a more straightforward or elegant solution I would love to hear it! Any general guidance would be greatly appreciated!

-kevin.

3 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

This is certainly one possible approach. What specifically is going wrong?

(Try and move to latest ifort version if you can - polymorphic components have been a bountiful source of compiler bugs. Some issues remain with 13.0.1 that can still cause me much trouble, but on a sunny day with the wind in the right quarter it is often possible to workaround things.)

Depending on what you want to do (universal qualifier in all following discussion) you may be better off moving the addValue and getValue procedures down the inheritance tree one step, such that they don't have to be type bound and move the select type up a level to work on the value component of your container. For clarity of code, keep the same (generic) procedure/binding names. For example:


  ! I want to do something with message_array(2)

  ! (assuming the component in the container is called contents

  ! per your use, not value per your declaration)

  SELECT TYPE (item => message_array(2)%content)

  TYPE IS (realvar)

    ! Modified version of the procedure that formerly implemented the addValue

    ! binding - but now the first argument can be non-polymorphic realvar and the second

    ! argument can be REAL rather than CLASS(*).

    call addValue(item, some_real_number)

  TYPE IS (charvar)

    ! etc.- specific procedure resolution can be on the basis of the first (charvar) and

    ! second arguments (character).

    ....

  END SELECT

Alternatively, simply store the data in a CLASS(*) component inside the variable type, and ditch the inheritance hierarchy all together. (This restricts the rank of the stuff to be stored to be the same - but that is the case with the value components of your two extensions currently.)

Third possibility is to consider what you are going to do with the value returned in your original getValue - perhaps you are better off writing the bindings to do that.

Thank you very much for the suggestion(s)!!

The pseudo-code you provided was a great perspective change. Can/should I then overload addValue with respective signatures (dummy variables) to handle the different "item"s and intrinsic types? (I'm assuming FORTRAN 2003 can handle overloading, but I've been losing a number of assumptions lately.) It looks like I need to set up an interface block to handle overloading procedures.

At one point I tried the CLASS(*) in the variable option you mentioned, but had trouble reallocating it to add values. This attempt and others that have failed are in a graveyard of code that am going to formalize into test cases to share in this forum. I have had catastrophic internal compile errors that I should probably report but want to give good test cases.

Thx again!

Login to leave a comment.