Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
Beginner

Another issue with user defined assignment

Dear all,

there is an issue, I've found with user defined assignment when using the ifort compiler (version 16.0). Let's have a basic type with user defined assignment (as TBP) and an extending type which does not define its own assignment. When a non-polymorphic instance of the extended type is defined, an assignment to it invokes the user defined assignment of the base type. When I understood the explanations of Malcom Cohen (as response to my nagfor compiler bug report in this matter), this should, however, not happen. So, in the example below, the assignBasic() routine should not be invoked (as it happens in the ifort compiled binary).

module typedef
  implicit none

  type :: Basic
  contains
    procedure :: assignBasic
    generic :: assignment(=) => assignBasic
  end type Basic

  type, extends(Basic) :: Extended
  end type Extended

contains

  subroutine assignBasic(this, other)
    class(Basic), intent(out) :: this
    type(Basic), intent(in) :: other

    print "(A)", "assignBasic invoked"

  end subroutine assignBasic

end module typedef


program test
  use typedef
  implicit none

  type(Extended) :: ext
  print "(A)", "Non-polymorphic assignment"
  ext = Extended()

end program test

 

0 Kudos
7 Replies
Highlighted
Employee

Thank you for the report. I

Thank you for the report. I'll investigate and route to Development.

(Internal tracking id: DPD200414314)

0 Kudos
Highlighted
Black Belt

I think the behaviour is

I think the behaviour is subject to interp F08/0146. 

The issue is that the `other` dummy argument of the subroutine implementing defined assignment is not polymorphic, not the polymorphic/non-polymorphic nature of the thing being assigned.  When it is not polymorphic, that `other` dummy argument, which is of type `Base`, is not type compatible with a right hand side object of type Extended.  Consequently the overall assignment on line 32 is intrinsic.

But the process of intrinsic assignment invokes defined assignment for components of the type.  Depending on F08/0146, this includes the parent component, which is of type `Base` and is therefore type compatible with the `other` dummy argument.

I think it makes little sense for the language to not consider defined assignment for the parent component ahead of subcomponents of that parent component i.e., I hope the result of the interp is such that you should see "assignBasic invoked".  As a principle, objects of derived type should not break up into their components if there is the possibility of operating on the object as an aggregate.

 

0 Kudos
Highlighted
Beginner

As always, Ian can formulate

As always, Ian can formulate the problem much more accurate, than I. Ian, I really appreciate your capability to reformulate my confusing explanations to a clear statement about the real problem. :-)

Just out of curiosity: The interpretation you refer to (the one you hope for), has that been already decided on in any form? Or is it something which is still being debated? Just because from the answer I got from Malcom Cohen, I had the feeling, he wants to point out explicitely, that intrinsic assignment is done nonancestor-componentwise, so assignBasic should not be invoked in the example above. But I'd for sure find your interpretation more satisfying, provided I can rely on the fact, that all compilers do it the same way.

0 Kudos
Highlighted

F08/0146 is still being

F08/0146 is still being considered. It failed its initial meeting vote in October 2015. I can't find any indication it has been reconsidered since then.

Retired 12/31/2016
0 Kudos
Highlighted
Employee

I routed the case and

I routed the case and extended comments to Development.

0 Kudos
Highlighted

For everyone's information,

For everyone's information, here's the content of interp F08/0146 including committee member Tom Clune's view of "how it should work", which Malcolm Cohen of NAG disagrees with. This is likely to be revisited at next month's meeting.

NUMBER: F08/0146
TITLE: Does intrinsic assignment copy inherited components twice?
KEYWORDS: Intrinsic assignment, Type extension
DEFECT TYPE: Erratum
STATUS: J3 consideration in progress

QUESTION:

Consider

  Type base
    Integer a
  End Type
  Type,Extends(base) :: ext
    Real b
  End Type
  ...
  Type(ext) x,y
  ...
  x = y

According to 7.2.1.2 Intrinsic assignment statement, p13,

  "An intrinsic assignment where the variable is of derived type is
   performed as if each component of the variable were assigned from
   the corresponding component of <expr>"

This would seem to indicate that the above assignment "x = y" is
interpreted as the following (in unspecified order):

  x%a = y%a
  x%b = y%b
  x%base = y%base

and the assignment to x%base is treated as if it were

  x%base%a = y%base%a

thus assigning to x%a twice, which does not seem to make sense.  If a
type is extended more than once, there can be a plethora of ancestor
components inheritance-associated with a component, resulting in that
component being assigned many times.

Q1. Are these per-component assignment semantics intended to apply to
    ancestor components (and thus produce multiple assignments for
    inherited components).

It is particularly problematic if the components have type-bound
defined assignment, for example consider the program

  Module damod
    Type datype
    Contains
      Procedure asgnda
      Generic :: Assignment(=) => asgnda
    End Type
  Contains
    Subroutine asgnda(a,b)
      Class(datype),Intent(Out) :: a
      Class(datype),Intent(In) :: b
      Print *,'Hello asgnda'
    End Subroutine
  End Module
  Module types
    Use damod
    Type base
      Type(datype) c
    End Type
    Type,Extends(base) :: ext
    End Type
  End Module
  Program test
    Use types
    Type(ext) :: x = ext(datype()), y
    y = x
  End Program

Q2. Does this program print "Hello asgnda" once or twice?

ANSWER:

A1. This sentence was not intended to apply to ancestor components; an
    edit is supplied to correct this oversight.

A2. The program should print "Hello asgnda" only once.

EDITS to 10-007r1:

[156:3] 7.2.1.2 Intrinsic assignment statement, p13,
        Between "performed as if each" and "component"
        insert "nonancestor".

SUBMITTED BY: Malcolm Cohen

HISTORY: 15-218    m208  F08/0146  Submitted
                   m208  Failed J3 meeting vote 3-4

<< start of Tom Clune's comments on F08/0146>>

 expect defind assignment to work like finalization on the parent
of an extended type [15-007r2 4.5.6.2 78:19] - if the parent has
defined assignment, it is called on the parent and not on its components.

From: Tom Clune
Sent: Friday, October 16, 2015 9:41 AM

Attached below is a motivating example of my objection to the interp
we discussed on Tuesday. (Paper 218 I think.)

I also have a few additional arguments to bolster my case:

1) If I understand correctly, the semantics of finalization work on
   the base object, not on the components of the base object.  In
   15-007r2.pdf  4.5.6.2 The finalization process (78:19), we have:  

    "If the entity is of extended type and the parent type is
     finalizable, the parent component is finalized."

   The proposed interp seems to run contrary to this treatment of the
   base as a coherent entity.

2) Not that it really matters, but C++ implicit copy/assignment semantics
   also use the copy/assignment operator for the base object, not its
   components.

3) Just to reiterate my point on Tuesday.   The proposed interp in 218
   adds considerable burden to implementors of type extensions.   If a
   base type overrides default assignment, then the type extension
   almost certainly must as well, or risk ruining some invariant property
   that the base otherwise preserves.  Further, this burden propagates
   down the inheritance hierarchy.  Perhaps on some occasions, the
   implementor may find that default assignment is actually ok, but it
   would require looking "inside" the base type implementation.  (And I
   cannot construct a plausible motivating example of this situation,
   though I've also not tried very hard.)

! The following is an example of how many users intuitively expect
! instrinsic assignment of an extended type when the ancestor type
! has a derived assignment.  

! Here, we desire _deep_ copy semantics for type base, which happens to
! use POINTER components.  In this instance, an allocatable component
! could fix the issue, but consider the case of a base type which
! implements a binary tree, and pointer components were thus much harder
! to avoid.


module the_classes

   type :: base
      integer, pointer :: ptr => null()
   contains
      procedure :: equals
      generic :: assignment(=) => equals
   end type base

   ! We now extend the base class in a relatively trivial manner.
   ! There needs to be at least one new component or the casual
   ! observer might think that the assignment defined below
   ! could just use CLASS for the RHS argument and allow inheritance
   ! to do the "right thing" (TM).  Often we would like the new components
   ! defined in the extension to be copied as well, and that
   ! approach becomes inadequate.
   type, extends(base) :: child
      character(len=:), allocatable :: name
   end type child

contains

   subroutine equals(a, b)
      class (base), intent(out) :: a
      type (base), intent(in) :: b

      allocate(a%ptr)
      a%ptr = b%ptr
      
   end subroutine equals

end module the_classes


program main
   use the_classes
   implicit none

   type (child)  :: c1, c2

   allocate(c1%ptr)
   c1%ptr = 1
   c1%name = 'Alice'

   c2 = c1
   ! Because we want "deep copy" semantics, we want to be able to change
   ! c1%ptr without impacting c2

   c1%ptr = 2

   print*,'Results:'
   print '(10x,a10,1x,i1)', c2%name, c2%ptr
   print '(a9,1x,a10,1x,i1)', 'Expected: ','Alice',1
   
end program main

<<end of Tom Clune's comment on F08/0146>>
Retired 12/31/2016
0 Kudos
Highlighted
Valued Contributor III

Quote:Steve Lionel (Intel)

Steve Lionel (Intel) wrote:

For everyone's information, here's the content of interp F08/0146 including committee member Tom Clune's view of "how it should work", which Malcolm Cohen of NAG disagrees with. This is likely to be revisited at next month's meeting.

NUMBER: F08/0146
TITLE: Does intrinsic assignment copy inherited components twice?
..

This would seem to indicate that the above assignment "x = y" is
interpreted as the following (in unspecified order):

  x%a = y%a
  x%b = y%b
  x%base = y%base

and the assignment to x%base is treated as if it were

  x%base%a = y%base%a

thus assigning to x%a twice, which does not seem to make sense.  ..

But, Steve, what about "Inheritance association" (16.5.4 in WD 1539-1 J3-10-007r1 document for Fortran 2008)?

  8 16.5.4 Inheritance association
 9 1 Inheritance association occurs between components of the parent component and components inherited by type 
10   extension into an extended type (4.5.7.2). This association is persistent; it is not affected by the accessibility of
11   the inherited components.

Doesn't inheritance association essentially imply there is really no such thing as "x%a", that it essentially only has "association" that is persistent with "x%base%a"?  Therefore, isn't the assignment "x%a = y%a" meaningless, that it is only "x%base%a = y%base%a" which is relevant?

So how does the "twice" part of the question addressed by the interp even arise?

 

0 Kudos