Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

defined elemental assignment and re-allocation of allocatable arrays

113 views
Skip to first unread message

Dominik Gronkiewicz

unread,
Feb 3, 2021, 6:07:05 AM2/3/21
to
Hello!

I have been struggling with the following issue. Consider the code:

module m

type :: typ
integer :: a, b
contains
procedure :: assign
! comment/uncomment this line
generic :: assignment(=) => assign
end type

interface typ
module procedure ctor
end interface

contains

elemental function ctor(n) result(self)
type(typ) :: self
integer, intent(in) :: n
integer :: i

self%a = n
self%b = n**2
end function

elemental subroutine assign(self, them)
class(typ), intent(inout) :: self
type(typ), intent(in) :: them

self%a = them%a
self%b = them%b
end subroutine

end module m

program test
use m
block
type(typ), allocatable :: arr(:)

allocate(arr(2))
arr(1) = typ(1)
arr(2) = typ(2)
print *, size(arr), ':', arr

arr = [arr, typ(3)]
print *, size(arr), ':', arr

arr = [typ(4)]
print *, size(arr), ':', arr
end block

end program

When the intrinsic assignment is used (the generic line is commented), the result is correct:

2 : 1 1 2 4
3 : 1 1 2 4 3 9
1 : 4 16

However, when the defined assignment is used (trivial in this case, in my actual code it allocates and copies a memory buffer), i get the incorrect result with gfortran 10.2.1:

2 : 1 1 2 4
2 : 1 1 2 4
2 : 4 16 2 4

It looks as if the assignment was perfomed in a dumb way, without performing the reallocation of the LHS. In this case:

1/ is this a compiler bug
2/ how to define an assignment that would transparently work as expected (in this case, same as if the intrinsic assignment was used)

Thanks in advance!
Dominik

Dominik Gronkiewicz

unread,
Feb 3, 2021, 7:22:43 AM2/3/21
to
Alternatively, consider the following program.

program test
use m

block
type(typ), allocatable :: arr(:)

allocate(arr(2))
arr(1) = typ(1)
arr(2) = typ(2)
print *, size(arr), ':', arr

deallocate(arr)
arr = [typ(1), typ(2)]
print *, size(arr), ':', arr

end block

end program

With intrinsic assignment, the result is correct:

2 : 1 1 2 4
2 : 1 1 2 4

Whereas, with the defined assignment... it segfaults on the line:

arr = [typ(1), typ(2)]

since the array on the LHS is not allocated at this point.

Is this a loophole in the standard? It seems like depending on whether TYP has or has not a defined assignment, the behavior can be strikingly different, which looks like a mistake to me.

Dominik

gah4

unread,
Feb 3, 2021, 7:59:24 AM2/3/21
to

Interesting.

It seems to me that it could be a feature.
That is, that it is the responsibility of the defined assignment to, among others,
do the reallocation if it wants it done.

Can defined assignment, if needed, create a different sized array than the right side?

As well as I know, part of elemental is that you have to get the sizes right in cases other than assignment.

Themos Tsikas

unread,
Feb 3, 2021, 8:04:47 AM2/3/21
to
On Wednesday, 3 February 2021 at 12:22:43 UTC, gro...@gmail.com wrote:
> Is this a loophole in the standard? It seems like depending on whether TYP has or has not a defined assignment, the behavior can be strikingly different, which looks like a mistake to me.
>
> Dominik

Modern Fortran Explained (OUP by Metcalf, Reid, Cohen) says
"Automatic reallocation only occurs for normal intrinsic assignment, and not for defined assignment or for where constructs."

I do not see any mention of current work on the Standard to change this, perhaps someone else is aware?

Themos Tsikas, NAG Ltd.

Themos Tsikas

unread,
Feb 3, 2021, 8:54:34 AM2/3/21
to
"10.2.1.4. Defined assignment" contains the wording

"If the subroutine is elemental, x2 shall be conformable with x1 ."

(for a subroutine defining the defined assignment x1 = x2)

Dominik Gronkiewicz

unread,
Feb 3, 2021, 8:55:16 AM2/3/21
to
środa, 3 lutego 2021 o 14:04:47 UTC+1 Themos Tsikas napisał(a):
> Modern Fortran Explained (OUP by Metcalf, Reid, Cohen) says
> "Automatic reallocation only occurs for normal intrinsic assignment, and not for defined assignment or for where constructs."

It looks like automatic reallocation is a more narrow feature than I anticipated. I would think that since elemental procedures are "safe" to use in a concurrent way, the behavior of elemental defined assignment would be consistent with intrinsic assignment. This inconsistency is a bit disappointing but thank you for the answer. It seems like defined assignments are best to be avoided.

Dominik

FortranFan

unread,
Feb 3, 2021, 9:16:53 AM2/3/21
to
On Wednesday, February 3, 2021 at 7:22:43 AM UTC-5, gro...@gmail.com wrote:

> ..
> Is this a loophole in the standard? It seems like depending on whether TYP has or has not a defined assignment, the behavior can be strikingly different, which looks like a mistake to me. ..

@Dominik,

I suggest you try the Fortran Discourse site for such discussions mainly because code formatting and syntax highlighting can be preserved:
https://fortran-lang.discourse.group/

As pointed out by @gah4, it's more like a "feature" in the standard. Considering the semantics of ELEMENTAL generally (usually pure, shape matching, or one a scalar) and polymorphic arguments (cannot be INTENT(OUT) in pure procedures) and type-bound procedures (passed dummy argument has to be polymorphic but not INTENT(OUT) nor ALLOCATABLE), you effectively need to stay away from generic binding for ASSIGNMENT(=) (and other defined operations) IF YOU need the same facilities as intrinsic assignment such as allocation and/or reallocation of objects of RANK 1 and greater.

Better if you can manage with intrinsic assignments, but if you require defined assignments due to some requirements with special instructions, then go with Fortran 90 style generic interfaces which you can combine with Fortran 2018 to achieve a bit of code brevity..

Shown below is a variant of your code, I suggest you review it closely. By the way, Intel compiler with support of full Fortran 2018 (bugs notwithstanding) as available for FREE in their Intel oneAPI toolkit is what the code below runs with:
--- begin modified example ---
module m

type :: typ
integer :: a, b
contains
procedure :: assign
! comment/uncomment this line
!generic :: assignment(=) => assign
end type

interface typ
module procedure ctor
end interface

generic :: assignment(=) => rank1_assign, rank2_assign !<-- Use generic interface instead of bindings with TBP

contains

elemental function ctor(n) result(self)
type(typ) :: self
integer, intent(in) :: n
integer :: i

self%a = n
self%b = n**2
end function

elemental subroutine assign(self, them)
class(typ), intent(inout) :: self
type(typ), intent(in) :: them

self%a = them%a
self%b = them%b
end subroutine

subroutine rank1_assign(self, them)

type(typ), allocatable, intent(inout) :: self(:)
type(typ), intent(in) :: them(:)

! Code below can be in an INCLUDE file
!Local variables
logical :: realloc
print *, "In rank1_assign:"
realloc = .true.
if ( allocated(self) ) then
if ( size(self) == size(them) ) realloc = .false.
if ( realloc ) deallocate( self )
end if
if ( realloc ) then
allocate( self, source=them )
else
call self%assign( them )
end if

end subroutine rank1_assign

subroutine rank2_assign(self, them)

type(typ), allocatable, intent(inout) :: self(:,:)
type(typ), intent(in) :: them(:,:)

! Code below can be in an INCLUDE file
!Local variables
logical :: realloc
print *, "In rank2_assign:"
realloc = .true.
if ( allocated(self) ) then
if ( size(self) == size(them) ) realloc = .false.
if ( realloc ) deallocate( self )
end if
if ( realloc ) then
allocate( self, source=them )
else
call self%assign( them )
end if

end subroutine rank2_assign

end module m

program test
use m
blk1: block
type(typ), allocatable :: arr(:)

print *, "Block 1: cases with Rank 1 and *RE*allocation on assignment"
allocate(arr(2))
arr(1) = typ(1)
arr(2) = typ(2)
print *, size(arr), ':', arr

arr = [arr, typ(3)]
print *, size(arr), ':', arr

arr = [typ(4)]
print *, size(arr), ':', arr
end block blk1
print *
blk2: block
type(typ), allocatable :: arr(:)

print *, "Block 2: cases with Rank 1 and *A*llocation on assignment"
allocate(arr(2))
arr(1) = typ(1)
arr(2) = typ(2)
print *, size(arr), ':', arr

deallocate(arr)
arr = [typ(1), typ(2)]
print *, size(arr), ':', arr

end block blk2
print *
blk3: block
type(typ), allocatable :: arr(:,:)

print *, "Block 3: cases with Rank 2 and *A*llocation on assignment"
allocate(arr(2,2))
arr(1,1) = typ(1)
arr(2,1) = typ(2)
arr(1,2) = typ(3)
arr(2,2) = typ(4)
print *, shape(arr), ':', arr

deallocate(arr)
arr = reshape( [typ(1), typ(2), typ(3),typ(4)], shape=[2,2] )
print *, shape(arr), ':', arr

end block blk3
end program
--- end example ---

Program output with Intel Fortran Classic as part of Intel oneAPI:
--- begin output ---
C:\Temp>ifort /standard-semantics /warn:all /stand:f18 a.f90
Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel(R) 64, Version 2021.1.2 Build 20201208_000000
Copyright (C) 1985-2020 Intel Corporation. All rights reserved.

a.f90(22): remark #7712: This variable has not been used. [I]
integer :: i
-----------------^
Microsoft (R) Incremental Linker Version 14.26.28806.0
Copyright (C) Microsoft Corporation. All rights reserved.

-out:a.exe
-subsystem:console
a.obj

C:\Temp>a.exe
Block 1: cases with Rank 1 and *RE*allocation on assignment
2 : 1 1 2 4
In rank1_assign:
3 : 1 1 2 4 3 9
In rank1_assign:
1 : 4 16

Block 2: cases with Rank 1 and *A*llocation on assignment
2 : 1 1 2 4
In rank1_assign:
2 : 1 1 2 4

Block 3: cases with Rank 2 and *A*llocation on assignment
2 2 : 1 1 2 4 3 9 4 16
In rank2_assign:
2 2 : 1 1 2 4 3 9 4 16

C:\Temp>
--- end output ---

FortranFan

unread,
Feb 3, 2021, 9:51:08 AM2/3/21
to
On Wednesday, February 3, 2021 at 8:04:47 AM UTC-5, Themos Tsikas wrote:

> ..
> Modern Fortran Explained (OUP by Metcalf, Reid, Cohen) says
> "Automatic reallocation only occurs for normal intrinsic assignment, and not for defined assignment or for where constructs."
> ..

On Wednesday, February 3, 2021 at 8:55:16 AM UTC-5, gro...@gmail.com wrote:

> .. It seems like defined assignments are best to be avoided.

A key aspect with defined assignment is the author of the assignment essentially has to be willing take on considerable responsibility and think through the scenarios where the defined assignment will be consumed and factor them all into the design of the defined assignment. Few facilities will be "automatic" under the circumstances, as alluded to by Modern Fortran Explained.

As I show in the thread above (https://groups.google.com/g/comp.lang.fortran/c/yAILeFjCGuQ/m/efkzh8P6AwAJ), that can be a lot of work for authors.

Thus "USER BEWARE" will remain for the users of defined assignments in Fortran!

Dominik Gronkiewicz

unread,
Feb 3, 2021, 10:09:35 AM2/3/21
to
> As I show in the thread above (https://groups.google.com/g/comp.lang.fortran/c/yAILeFjCGuQ/m/efkzh8P6AwAJ), that can be a lot of work for authors.
>
> Thus "USER BEWARE" will remain for the users of defined assignments in Fortran!

Thank you for the piece of the code. I considered that solution but since I really want to remain rank-agnostic, I might just either stick with elemental and do the re-allocation wherever I use the assignment or rewrite my object to use allocatables rather than pointers (which should be good with intrinsic assignments).

Dominik

Dominik Gronkiewicz

unread,
Feb 3, 2021, 10:51:30 AM2/3/21
to
Also, thank you for the link to the discourse group. Is it the new recommended place to discuss Fortran?

I will continue the topic in this thread and migrate to the board with future questions.

I have a follow-up question: I figured that using a wrapper might work (the defined assignment will be used to copy baby objects by the intrinsic assignment of the mother object), but that seems to not be the case and the behavior persists. (module M remains the same)

program test
use m

type :: typ_wrap
type(typ) :: obj
end type

block
type(typ_wrap), allocatable :: arr(:)

allocate(arr(2))
arr(1) = typ_wrap(obj=typ(1))
arr(2) = typ_wrap(obj=typ(2))
print *, size(arr), ':', arr

arr = [arr, typ_wrap(obj=typ(3))]
print *, size(arr), ':', arr

arr = [typ_wrap(obj=typ(4))]
print *, size(arr), ':', arr
end block

end program

Why is that the case? I haven't found anything in standard about such behavior. Why is the intrinsic assingment not invoked?

Dominik

Themos Tsikas

unread,
Feb 3, 2021, 11:06:23 AM2/3/21
to
On Wednesday, 3 February 2021 at 14:16:53 UTC, FortranFan wrote:
> generic :: assignment(=) => rank1_assign, rank2_assign !<-- Use generic interface instead of bindings with TBP

This f2018 feature can be written in longer form for compilers that do not implement it yet (e.g. nagfor, gfortran):

interface assignment(=)
module procedure rank1_assign, rank2_assign
end interface assignment(=)

Themos Tsikas (NAG Ltd)

FortranFan

unread,
Feb 3, 2021, 11:44:48 AM2/3/21
to
On Wednesday, February 3, 2021 at 10:51:30 AM UTC-5, gro...@gmail.com wrote:

> ..
> Why is that the case? I haven't found anything in standard about such behavior. Why is the intrinsic assingment not invoked?
> ..

I agree re: the standard, your code with the wrapper looks conformant to me. In this case, I will contend it's a gfortran compiler bug.

See this announcement at the Fortran Discourse site re: Intel's latest - see if you can try it out with that compiler:
https://fortran-lang.discourse.group/t/intel-releases-oneapi-toolkit-free-fortran-2018/471

My hunch is your wrapper case will work with latest IFORT. Not that I'm trying to convey Intel compiler is without bugs - I've many, many open bug reports with them too but with the combo behavior of intrinsic assignment for a derived type but defined assignments for one or more components of said type, Intel Fortran support has fixed the tickets I've submitted with them so I'm giving them the better benefit of doubt.

FortranFan

unread,
Feb 3, 2021, 11:50:01 AM2/3/21
to
On Wednesday, February 3, 2021 at 10:51:30 AM UTC-5, gro...@gmail.com wrote:

> .. Is it the new recommended place to discuss Fortran? ..

No one can make such a recommendation, It's just that with text markdown in posts and replies and actual code snippets, formatting, Fortran syntax highlighting, images, file attachments, etc., it's easier to communicate and engage on sites such as that Fortran Discourse one.

Themos Tsikas

unread,
Feb 3, 2021, 11:57:19 AM2/3/21
to
On Wednesday, 3 February 2021 at 15:51:30 UTC, gro...@gmail.com wrote:
> I have a follow-up question: I figured that using a wrapper might work (the defined assignment will be used to copy baby objects by the intrinsic assignment of the mother object), but that seems to not be the case and the behavior persists. (module M remains the same)
> Why is that the case? I haven't found anything in standard about such behavior. Why is the intrinsic assingment not invoked?

I believe this may be a bug in gfortran.

Themos Tsikas, NAG Ltd.

Dominik Gronkiewicz

unread,
Feb 3, 2021, 12:49:56 PM2/3/21
to
I downloaded ifort and indeed, it seems to work as expected. I will (try) to file a bug with gfortran, then. Thank you :)

gah4

unread,
Feb 3, 2021, 6:28:47 PM2/3/21
to
On Wednesday, February 3, 2021 at 6:16:53 AM UTC-8, FortranFan wrote:

(snip)

> As pointed out by @gah4, it's more like a "feature" in the standard. Considering the semantics of ELEMENTAL generally (usually pure, shape matching, or one a scalar) and polymorphic arguments (cannot be INTENT(OUT) in pure procedures) and type-bound procedures (passed dummy argument has to be polymorphic but not INTENT(OUT) nor ALLOCATABLE), you effectively need to stay away from generic binding for ASSIGNMENT(=) (and other defined operations) IF YOU need the same facilities as intrinsic assignment such as allocation and/or reallocation of objects of RANK 1 and greater.

Thanks. After writing that, I tried to tell from the standard whether you can qualify GENERIC based on ALLOCATABLE, and so write the routine to do allocate on assignment. (Rank is a different question.)

If you can do it by ALLOCATABLE, then I think you write the routine to test the size, or just automatically reallocate even if the size doesn't change. In any case, it seems that does not happen with ELEMENTAL.
0 new messages