Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Error when allocating within a select type statement

AThar2
Beginner
2,624 Views

Can anybody explain why the following code is not permitted?

 

subroutine Allocation1(Vec)
    class(*), allocatable, intent(out)      :: Vec(:)

    select type(Vec)
    type is(real(8))
        allocate(Vec(10)); Vec = 0.D0
    type is(complex(8))
        allocate(Vec(10)); Vec = (0.D0,0.D0)
    type is(integer)
        allocate(Vec(10)); Vec = 0
    endselect
endsubroutine Allocation1

 

0 Kudos
28 Replies
Steve_Lionel
Honored Contributor III
2,180 Views

Because inside a SELECT TYPE, the associating entity does not have the ALLOCATABLE attribute.

11.1.3.3 Other attributes of associate names
2 1 Within an ASSOCIATE, CHANGE TEAM, or SELECT TYPE construct, each associating entity has the same
3 rank as its associated selector. The lower bound of each dimension is the result of the intrinsic function LBOUND
4 (16.9.109) applied to the corresponding dimension of selector. The upper bound of each dimension is one less
5 than the sum of the lower bound and the extent. The associating entity does not have the ALLOCATABLE or
6 POINTER attributes; it has the TARGET attribute if and only if the selector is a variable and has either the
7 TARGET or POINTER attribute.

0 Kudos
Juergen_R_R
Valued Contributor II
2,180 Views

Fortran 2018 standard, 11.1.11.2.8, execution of SELECT TYPE construct:

The other attributes of the associating entity are described in 11.1.3.3

Then, 11.1.3.3.1 says:

[...]  The associating entity does not have the ALLOCATABLE or POINTER attributes; [...]

 

0 Kudos
AThar2
Beginner
2,180 Views

Thanks for your replies

 

@s

Two questions

 

1) based on what you  mentioned, I CANNOT allocate an object/variable based on the class(*) and its dynamic argument type?

2) If that is the case, I am struggling to understand what is the purpose for the unlimited polymorphic feature, or when is it beneficial to use?

 

0 Kudos
Juergen_R_R
Valued Contributor II
2,180 Views

You can, I think you have to do something like this (not tested):

subroutine Allocation1(vec_in,Vec_out)
  class(*), allocatable, intent(in)      :: Vec_in(:)
  class(*), allocatable, intent(out)     :: Vec_out(:)  
  
  select type(Vec_in)
  type is(real(8))
     allocate(Vec_out(10), source=vec_in); Vec_out = 0.D0
  type is(complex(8))
     allocate(Vec_out(10), source=vec_in); Vec_out = (0.D0,0.D0)
  type is(integer)
     allocate(Vec_out(10), source=vec_in); Vec_out = 0
  endselect
end subroutine Allocation1

 

0 Kudos
FortranFan
Honored Contributor III
2,180 Views

As to "why": think of it as a safeguard given the unlimited polymorphic gives you "enough rope to shoot yourself in the foot"

But are you looking for something like this which the standard allows but it's useless with intrinsic types given the facilities available in the language such as with ALLOCATE statement itself:

   subroutine Allocation1(Vec)
      class(*), allocatable, intent(out) :: Vec(:)
      integer, parameter :: SZ_VEC = 10
      integer :: i
      select type ( Associating_Entity => Vec )
         type is (real(kind=RK))
            Vec = [( 0.0_rk, i=1,SZ_VEC )]
         type is(complex(kind=RK))
            Vec = [( (0.0_rk,0.0_rk), i=1,SZ_VEC )]
         type is(integer(kind=IK))
            Vec = [( 0_ik, i=1,SZ_VEC )]
      end select
   end subroutine Allocation1

Note the point about "associating entity" in the standard per posts upthread; it's that which does not have the ALLOCATABLE attribute.

Separate suggestions to keep in mind: no 'magic numbers' in code and use defined kinds of intrinsic types.  See this Dr Fortran blog: https://software.intel.com/en-us/blogs/2017/03/27/doctor-fortran-in-it-takes-all-kinds

0 Kudos
AThar2
Beginner
2,180 Views

@FortranFan,

I don't understand the code entirely.

A) your `Associating_Entity` is that not just a different name of Vec, kind of an auxiliary name? Also, you don't seem to use it. So what were exactly trying to show me? You write it does not have an allocatable attribute, but VEC does?

B) Where is Vec allocated? also, when vec is returned by the subroutine does it contain the values it received within the select type statement.

In many cases it seems that whenever the code leave the select type clause everything if "forgotten". I h

 

 

 

 

 

0 Kudos
AThar2
Beginner
2,180 Views

What  I meant by forgotten is the following code. I hoped the simple code snippet will illuminate my original problem, but it does look like. My purpose is that I want my subroutine CREATE to work with real and  integer pointer. I am checking with a select type at the beginning of my routine whether kind it is.  As you see, when look if my pointer it associated after my create call, it says false , if I ask for its size it gives me a zero even though inside CREATE it gives 10, which is the correct size.

      program TEST_STACK
         use, INTRINSIC ::ISO_C_BINDING
         use mpi

      implicit none
      integer, allocatable :: partners(:)            ! - Rank no. of partners
      integer, allocatable :: partners_map(:)        ! - Global Partner rank no. (used in multi-node parallelisation)

      !---  Parameters (They should not be changed ! )
      integer, parameter   :: whoisroot   = 0  ! - Root always 0 here
      !---  General parallel
      integer              :: whoami                 ! - My rank
      integer              :: mpi_nproc              ! - no. of procs
      integer              :: mpierr                 ! - Error status
      integer              :: status(MPI_STATUS_SIZE)! - For MPI_RECV
      !---  Shared memory stuff
      integer              :: whoami_shm             ! - Local rank in shared memory group
      integer              :: mpi_shm_nproc          ! - No. of procs in Shared memory group
      integer              :: no_partners            ! - No. of partners for share memory
      integer              :: info_alloc
      !---  MPI groups
      integer              :: world_group            ! - All procs accross all nodes
      integer              :: shared_group           ! - Only procs that share memory
      integer              :: MPI_COMM_SHM           ! - Shared memory communicators (for those in shared_group)

      integer                             :: win
      integer ::  size_
      integer, pointer, asynchronous     :: fptr(:)
      type F_PTR_INT
         integer, pointer :: recvbuf(:)
      end type F_PTR_INT

      type F_PTR_FLT
         real, pointer    :: recvbuf(:)
      end type F_PTR_FLT

      call MPI_INIT        ( mpierr )
      call MPI_COMM_RANK   ( MPI_COMM_WORLD, whoami, mpierr )

      call MPI_COMM_RANK   ( MPI_COMM_WORLD, whoami, mpierr )
      call MPI_COMM_SIZE   ( MPI_COMM_WORLD, mpi_nproc, mpierr)
      call MPI_COMM_SPLIT_TYPE( MPI_COMM_WORLD
     &                        , MPI_COMM_TYPE_SHARED
     &                        , 0
     &                        , MPI_INFO_NULL
     &                        , MPI_COMM_SHM
     &                        , mpierr )

      call MPI_COMM_RANK( MPI_COMM_SHM, whoami_shm, mpierr )
      call MPI_COMM_SIZE( MPI_COMM_SHM, mpi_shm_nproc, mpierr )
      call MPI_INFO_CREATE( info_alloc, mpierr )
      call MPI_INFO_SET( info_alloc
     &                    , "alloc_shared_noncontig"
     &                    , "true"
     &                    , mpierr )
      !

      size_ = 10


      call CREATE(fptr, win, size_)
      print*, associated(fptr)  ! - this says FALSE 
      print*, size(fptr)        ! - gives 0!!
     ! fptr(1) = 3               ! - hence this will cause segmentation fault

      call MPI_WIN_FREE(win, mpierr)

      contains

      subroutine create( fptr, win, size_ )
      implicit none
      integer, intent(in)                         :: size_
      integer, intent(out)                        :: win
      class(*), asynchronous,intent(out)          :: fptr(:)
      type(C_PTR)                                 :: ptr_buf
      integer(kind = MPI_ADDRESS_KIND)            :: size_bytes, lb
      integer(kind = MPI_ADDRESS_KIND)            :: size_of_kind
      integer                                     :: disp_unit
      integer :: kind_




      select type ( fptr )

      type is ( real )
         kind_ = MPI_REAL
      type is ( integer )
         kind_ = MPI_INTEGER
      end select

      call MPI_TYPE_GET_EXTENT(kind_, lb, size_of_kind, mpierr)

      size_bytes = size_ * size_of_kind
      disp_unit  = size_of_kind

      call MPI_WIN_ALLOCATE_SHARED( size_bytes
     &                            , disp_unit
     &                            , info_alloc
     &                            , MPI_COMM_SHM
     &                            , ptr_buf
     &                            , win
     &                            , mpierr )
      call C_F_POINTER(ptr_buf, fptr, (/size_/) )
      print*, size(fptr)  ! - gives me 10

      end subroutine create


      end program TEST_STACK

 

0 Kudos
FortranFan
Honored Contributor III
2,180 Views

AT90 wrote:

@FortranFan,

I don't understand the code entirely.

A) your `Associating_Entity` is that not just a different name of Vec, kind of an auxiliary name? Also, you don't seem to use it. So what were exactly trying to show me? You write it does not have an allocatable attribute, but VEC does?

B) Where is Vec allocated? also, when vec is returned by the subroutine does it contain the values it received within the select type statement.

In many cases it seems that whenever the code leave the select type clause everything if "forgotten". I h

 

 

 

 

 

As i mentioned, the code is useless for intrinsic types though I would argue it will be allowed per the standard.

1) The 'associated entity" is used in the code for the instructions to traverse the "TYPE IS" branches.

2) Look up Fortran 2008 feature of allocation upon assignment of polymorphic types.

0 Kudos
FortranFan
Honored Contributor III
2,180 Views

AT90 wrote:

What  I meant by forgotten is the following code. I hoped the simple code snippet will illuminate my original problem,  ..

The two codes do not appear equivalent: this latest one does not show the relevant dummy argument (fptr) to have the ALLOCATABLE/POINTER attribute for example.  You may want to work harder toward a minimal working example.  

0 Kudos
AThar2
Beginner
2,180 Views

Yes you are right. I removed it because otherwise the compiler forces me to declare the argument as unlimited polymorphic as well - and that was not my intention. I still wanted the argument to be either an integer or float pointer, while the dummy argument should have the unlimited poly. 

 

0 Kudos
AThar2
Beginner
2,180 Views

FortranFan wrote:

Quote:

AT90 wrote:

 

@FortranFan,

I don't understand the code entirely.

A) your `Associating_Entity` is that not just a different name of Vec, kind of an auxiliary name? Also, you don't seem to use it. So what were exactly trying to show me? You write it does not have an allocatable attribute, but VEC does?

B) Where is Vec allocated? also, when vec is returned by the subroutine does it contain the values it received within the select type statement.

In many cases it seems that whenever the code leave the select type clause everything if "forgotten". I h

 

 

 

 

 

 

 

As i mentioned, the code is useless for intrinsic types though I would argue it will be allowed per the standard.

1) The 'associated entity" is used in the code for the instructions to traverse the "TYPE IS" branches.

2) Look up Fortran 2008 feature of allocation upon assignment of polymorphic types.

 

2) is it then deallocated again upon the termination of the select type clause?

 

0 Kudos
FortranFan
Honored Contributor III
2,180 Views

AT90 wrote:

.. is it then deallocated again upon the termination of the select type clause?

No, it should not get deallocated.  Note though for what you show with intrinsic types, it's tough to foresee how the subprogram is going to work with unlimited polymorphic dummy argument.

0 Kudos
IanH
Honored Contributor III
2,180 Views

`CLASS(*)` is a facility for type agnostic storage at runtime.  Generally it is not useful for generic programming (i.e. writing procedures that do the same sort of operations regardless of the type of an argument).

INTENT(OUT) allocatable arguments are deallocated on procedure invocation.  Some of the example code fragments above then use such an deallocated dummy argument as the selector in SELECT TYPE. The dynamic type of an unallocated object is the same as its declared type, which may not be what the various code fragments expect.

Given the apparent intent behind the example code in post #8, the enhanced C interoperability features provided in F2018 may be helpful.  You could write an interoperable C function that takes a descriptor for a Fortran pointer, and does the appropriate MPI allocations and effective Fortran pointer argument association.  Easier integration with libraries such as MPI was one of the motivations for the enhanced C interoperability features.

Perhaps a simpler alternative, given that the example code only supports two type/kind combinations, is to simply write two Fortran specific procedures that each take a dummy argument of the specific type/kind that they support, and place those procedures behind a generic name.  To reduce the amount of "copy-paste" duplicated source code, you can extract the common syntax into an INCLUDE file.  It a bit hacky, but until the language supports generic programming, that's what you have to do.

  interface create
    procedure :: create_integer
    procedure :: create_real
  end interface create
  
  !....
  
  subroutine create_integer(fptr, win, size_)
    integer, intent(out), pointer :: fptr(:)
    integer, parameter :: kind_ = MPI_INTEGER
    include 'create_body.i90'
  end subroutine create_integer

  subroutine create_real(fptr, win, size_)
    real, intent(out), pointer :: fptr(:)
    integer, parameter :: kind_ = MPI_REAL
    include 'create_body.i90'
  end subroutine create_real

  !...
  ! In create_body.i90
  
  integer, intent(in) :: size_
  integer, intent(ou) :: win
  type(c_ptr) :: ptr_buf
  integer(MPI_ADDRESS_KIND) :: size_bytes, lb
  integer(MPI_ADDRESS_KIND) :: size_of_kind
  integer :: disp_unit
  
  call MPI_TYPE_GET_EXTENT(kind_, lb, size_of_kind, mpierr)
  
  size_bytes = size_ * size_of_kind
  disp_unit  = size_of_kind
  
  call MPI_WIN_ALLOCATE_SHARED(  &
      size_bytes,  &
      disp_unit,  &
      info_alloc,  &
      MPI_COMM_SHM,  &
      ptr_buf,  &
      win,  &
      mpierr )
  call C_F_POINTER(ptr_buf, fptr, (/size_/) )

(Be mindful that the forum's code formatting component ocassionally mutilates code.)

0 Kudos
AThar2
Beginner
2,180 Views

@IanH

thank you very much. The first paragraph has probably saved quite some time. 

 

Regarding your second paragraph w.r.t intent(out) allocatables. I am not able to understand fully what you mean? Can you please elaborate. 

 

Concerning the c-interoperability. Do you have a general example how this work, or can refer to another place?

finally, I am personally using the Book Modern Fortran Explained , which is good to explain the availabilities, however, are you aware of another book who perhaps goes more deeper into the modern part of Fortran?

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,180 Views

On the C++ side, a generic procedure relies on a template to generate the appropriate code for provided types. IOW you will not have a specific procedure generated for a type not seen. ?duh? you might be saying. Consider you having Fortran pass to C++ the generic arguments. Now you add types on the Fortran side. How is the C++ side going to handle this?

Currently the specific procedures with generic name may be what should be used.

However, if one is inclined to go outside the Language, consider something like BlockIt (http://fortranwiki.org/fortran/show/BlockIt) to pre-process your files. Also see: https://sourceforge.net/projects/blockit/

w.r.t allocatable(out)

Just what type do you expect an unallocated class(*) to be? Do you expect to maintain the class type of the last type allocated before deallocation? Or do you expect something analogous to a void type?

Jim Dempsey

0 Kudos
AThar2
Beginner
2,180 Views

w.r.t allocatable(out)

Just what type do you expect an unallocated class(*) to be? Do you expect to maintain the class type of the last type allocated before deallocation? Or do you expect something analogous to a void type?

Hello Jim,
I must say that I am completely lost with your questions here? What I wanted was to return an allocated object of the same type as the argument called with. But from the dummy argument, i.e. inside the procedure interface, the return type should be generic.

0 Kudos
FortranFan
Honored Contributor III
2,179 Views

AT90 wrote:

@FortranFan,

I don't understand the code entirely ..

@AT90,

Given your comment above and similar ones addressed to other readers, you may help yourself greatly if you can thoroughly review the Fifth Edition of Modern Fortran Explained, references in Dr Fortran blog https://software.intel.com/en-us/blogs/2013/12/30/doctor-fortran-in-its-a-modern-fortran-world, and other sources such as this: https://www.manning.com/books/modern-fortran

0 Kudos
FortranFan
Honored Contributor III
2,179 Views

AT90 wrote:

What  I meant by forgotten is the following code. ..

@AT90,

Keeping in mind the sources pointed to you in Quote #18 and the Fortran standard facilities with internal subprograms (aka those within CONTAINS section of a program or subprogram) and host associated entities (https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-host-association), are you complicating matters unnecessarily by trying to write subprograms that "return an allocated object of the same type as the argument called with. But from the dummy argument, i.e. inside the procedure interface, the return type should be generic"?  Meaning if your use case(s) is/are as shown in Quote #8, can you consider the following?

program TEST_STACK

   use, INTRINSIC :: ISO_C_BINDING
   use mpi

   implicit none
   integer, allocatable :: partners(:)            ! - Rank no. of partners
   integer, allocatable :: partners_map(:)        ! - Global Partner rank no. (used in multi-node parallelisation)

   !---  Parameters (They should not be changed ! )
   integer, parameter   :: whoisroot   = 0  ! - Root always 0 here
   !---  General parallel
   integer              :: whoami                 ! - My rank
   integer              :: mpi_nproc              ! - no. of procs
   integer              :: mpierr                 ! - Error status
   integer              :: status(MPI_STATUS_SIZE)! - For MPI_RECV
   !---  Shared memory stuff
   integer              :: whoami_shm             ! - Local rank in shared memory group
   integer              :: mpi_shm_nproc          ! - No. of procs in Shared memory group
   integer              :: no_partners            ! - No. of partners for share memory
   integer              :: info_alloc
   !---  MPI groups
   integer              :: world_group            ! - All procs accross all nodes
   integer              :: shared_group           ! - Only procs that share memory
   integer              :: MPI_COMM_SHM           ! - Shared memory communicators (for those in shared_group)

   integer :: win
   integer :: size_
   integer, pointer, asynchronous     :: fptr(:)
   type F_PTR_INT
      integer, pointer :: recvbuf(:)
   end type F_PTR_INT

   type F_PTR_FLT
      real, pointer    :: recvbuf(:)
   end type F_PTR_FLT

   call MPI_INIT        ( mpierr )
   call MPI_COMM_RANK   ( MPI_COMM_WORLD, whoami, mpierr )

   call MPI_COMM_RANK   ( MPI_COMM_WORLD, whoami, mpierr )
   call MPI_COMM_SIZE   ( MPI_COMM_WORLD, mpi_nproc, mpierr)
   call MPI_COMM_SPLIT_TYPE( MPI_COMM_WORLD
   &                        , MPI_COMM_TYPE_SHARED
   &                        , 0
   &                        , MPI_INFO_NULL
   &                        , MPI_COMM_SHM
   &                        , mpierr )

   call MPI_COMM_RANK( MPI_COMM_SHM, whoami_shm, mpierr )
   call MPI_COMM_SIZE( MPI_COMM_SHM, mpi_shm_nproc, mpierr )
   call MPI_INFO_CREATE( info_alloc, mpierr )
   call MPI_INFO_SET( info_alloc
   &                    , "alloc_shared_noncontig"
   &                    , "true"
   &                    , mpierr )
   !

   size_ = 10
   kind_ = MPI_INTEGER ! Why use SELECT TYPE when you have hard-wired the fptr type as INTEGER?
   call CREATE()
   print*, associated(fptr)  ! - this says FALSE
   print*, size(fptr)        ! - gives 0!!
   ! fptr(1) = 3               ! - hence this will cause segmentation fault

   call MPI_WIN_FREE(win, mpierr)

contains

   subroutine create()
   ! This INTERNAL subprogram operates on entities in its host

      ! Local variables
      type(C_PTR)                      :: ptr_buf
      integer(kind = MPI_ADDRESS_KIND) :: size_bytes, lb
      integer(kind = MPI_ADDRESS_KIND) :: size_of_kind
      integer                          :: disp_unit

      call MPI_TYPE_GET_EXTENT(kind_, lb, size_of_kind, mpierr)

      size_bytes = size_ * size_of_kind
      disp_unit  = size_of_kind

      call MPI_WIN_ALLOCATE_SHARED( size_bytes         &
                                   ,disp_unit          &
                                   ,info_alloc         &
                                   ,MPI_COMM_SHM       &
                                   ,ptr_buf            &
                                   ,win                &
                                   ,mpierr )
      call C_F_POINTER(ptr_buf, fptr, [ size_ ] )
      print*, size(fptr)  ! - gives me 10

   end subroutine create

end program TEST_STACK

 

0 Kudos
AThar2
Beginner
2,179 Views

jimdempseyatthecove wrote:

On the C++ side, a generic procedure relies on a template to generate the appropriate code for provided types. IOW you will not have a specific procedure generated for a type not seen. ?duh? you might be saying. Consider you having Fortran pass to C++ the generic arguments. Now you add types on the Fortran side. How is the C++ side going to handle this?

Currently the specific procedures with generic name may be what should be used.

Jim Dempsey

 

Jim, also what is meant by `specific procedures with generic name may be what should be used`

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,079 Views

>>Jim, also what is meant by `specific procedures with generic name may be what should be used`

On the C++ side:

template<typename T>
void fnAdd(T& A, T&B)
{
  A = A + B;
}
...
float a, b;
...
fnAdd(a,b); // generates a function taking two floats

If on the C++ side there is no instantiation of fnAdd with doubles as arguments, the C++ side will not have a defined function fnAdd that takes double references, and thus the Fortran side could not call the C++ side to perform the function for unknown type (un experienced by the template expansion). I know the answer is then on the receiver side on C++ you will need to add a case for the additional type. IOW in order to resolve this you must maintain consistencies of types on both sides. Even if ideally if one could define an abstract type in a common header file that can be used by both Fortran and C++, the template on the C++ side would have to be invoked with the types from the Fortran side.

I think the only resolutions are: a) to have an integrated compiler that accepts both languages, or b) have a JIT runtime compiler that can compile (once only) the template expansion(s) as needed.

Jim Dempsey

0 Kudos
Reply