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

Constructing an array of polymorphic variables

206 views
Skip to first unread message

spectrum

unread,
Jul 16, 2020, 12:13:07 AM7/16/20
to
Hello,

In the following code, I am trying to create an array containing polymorphic
variables by assigning some variable to each component.

# Below, memory leak is not the main topic of the problem, so I do not deallocate
pointers explicitly.

------------------ test1.f90 ----------------------
program main
use iso_c_binding
implicit none

type Base ; endtype
type, extends(Base) :: Type1 ; endtype
type, extends(Base) :: Type2 ; endtype

type BaseRef
class(Base), pointer :: p
endtype

type(BaseRef) :: refs( 2 )
class(Base), pointer :: p1, p2
integer i

allocate( Type1 :: p1 )
allocate( Type2 :: p2 )

refs( 1 )% p => p1
refs( 2 )% p => p2

do i = 1, 2
print *, "i = ", i
selecttype ( obj => refs( i )% p )
type is (Type1); print *, "-> Type1"
type is (Type2); print *, "-> Type2"
endselect
enddo
end
-------------------------------------------------------

For this code, gfortran gives my expected results.

$ gfortran-10 -Wall -Wextra -fcheck=all -fsanitize=address test1.f90
$ ./a.out
i = 1
-> Type1
i = 2
-> Type2

Now I would like to use allocatable arrays for "refs(:)" to use
automatic reallocation. The code becomes something like this:

-------------- test2.f90 ------------------------
program main
use iso_c_binding
implicit none

type Base ; endtype
type, extends(Base) :: Type1 ; endtype
type, extends(Base) :: Type2 ; endtype

type BaseRef
class(Base), pointer :: p !! (*)
endtype

type(BaseRef), allocatable :: refs(:) !! <--- here, refs(:) is changed to allocatable

class(Base), pointer :: p1, p2 !! (*)
integer i

allocate( Type1 :: p1 )
allocate( Type2 :: p2 )

refs = [ BaseRef( p1 ), BaseRef( p2 ) ] !! <-- my question is here

do i = 1, 2
print *, "i = ", i
selecttype ( obj => refs( i )% p )
type is (Type1); print *, "-> Type1"
type is (Type2); print *, "-> Type2"
endselect
enddo
end
-------------------------------------------

Compiling test2.f90 the same way gives my expected result, although
with a lot of warnings (which might be harmless, but not sure at the moment):

$ gfortran-10 -Wall -Wextra -fcheck=all -fsanitize=address test2.f90

test2.f90:20:0:

20 | refs = [ BaseRef( p1 ), BaseRef( p2 ) ]
|
Warning: 'refs.offset' may be used uninitialized in this function [-Wmaybe-uninitialized]
test2.f90:20:0: Warning: 'refs.dim[0].lbound' may be used uninitialized in this function [-Wmaybe-uninitialized]
test2.f90:20:0: Warning: 'refs.dim[0].ubound' may be used uninitialized in this function [-Wmaybe-uninitialized]
test2.f90:20:0:
...

$ ./a.out
i = 1
-> Type1
i = 2
-> Type2

Here, my question is this line:

refs = [ BaseRef( p1 ), BaseRef( p2 ) ]

where BaseRef( p1 ) is the default structure constructor that
creates a value of BaseRef by receiving a pointer argument p1.
In this case, is it OK to assume that p1 is "pointer assigned" to the
component of a temporary object (i.e., "t % p => p1" if I name a temporary as t)?
Or, should I assume that a structure constructor always work with "="
for all the components (with value semantics), i.e., t % p = p1 ?

FWIW, if I change the lines marked with (*) to alllocatables,
the code also compiles with similar warning messages, and gives
the same results (shown above).

spectrum

unread,
Jul 16, 2020, 12:37:29 AM7/16/20
to
I've also tried a corresponding code in D, which might be useful
to show what I wish to achieve with "test2.f90" above:

------------ test2.d --------------
import std.stdio;

class Base { void hello() {} }
class Type1 : Base { override void hello() { writeln("-> Type1"); } }
class Type2 : Base { override void hello() { writeln("-> Type2"); } }

void main()
{
Base[] arr;

arr = [ new Type1(), new Type2() ];

writeln( "arr = ", arr );

foreach (i, x ; arr) { writeln( "i = ", i ); x.hello(); }
}
------------------------------

$ ldc2 test2.d -ofa.out
$ ./a.out
arr = [test2.Type1, test2.Type2]
i = 0
-> Type1
i = 1
-> Type2

In the above case, class variables are "references", and
the assignment of class variables always means "pointer assignment".
But in the case of Fortran, pointers can be assigned either
with "=" or "=>", which seems not clear when default structure constructors
are used...

Thomas Koenig

unread,
Jul 16, 2020, 6:27:03 AM7/16/20
to
spectrum <septc...@gmail.com> schrieb:

> with a lot of warnings (which might be harmless, but not sure at the moment):
>
> $ gfortran-10 -Wall -Wextra -fcheck=all -fsanitize=address test2.f90
>
> test2.f90:20:0:
>
> 20 | refs = [ BaseRef( p1 ), BaseRef( p2 ) ]
> |
> Warning: 'refs.offset' may be used uninitialized in this function [-Wmaybe-uninitialized]
> test2.f90:20:0: Warning: 'refs.dim[0].lbound' may be used uninitialized in this function [-Wmaybe-uninitialized]
> test2.f90:20:0: Warning: 'refs.dim[0].ubound' may be used uninitialized in this function [-Wmaybe-uninitialized]

Does this warning go away with any -O option?

If so, this might be https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96047

spectrum

unread,
Jul 16, 2020, 11:45:39 AM7/16/20
to
On Thursday, July 16, 2020 at 7:27:03 PM UTC+9, Thomas Koenig wrote:
>
> Does this warning go away with any -O option?
> If so, this might be https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96047

Thanks much for your info, and yes, the behavior seems very similar: -O0 gives
the above warning messages, while they go away with any of the other -O* option
(and the compiled code gives the expected result, at least for the above output).

FortranFan

unread,
Jul 16, 2020, 3:33:43 PM7/16/20
to
On Thursday, July 16, 2020 at 12:13:07 AM UTC-4, spectrum wrote:

> ..
> Here, my question is this line:
>
> refs = [ BaseRef( p1 ), BaseRef( p2 ) ]
>
> where BaseRef( p1 ) is the default structure constructor that
> creates a value of BaseRef by receiving a pointer argument p1.
> In this case, is it OK to assume that p1 is "pointer assigned" to the
> component of a temporary object (i.e., "t % p => p1" if I name a temporary as t)?
> Or, should I assume that a structure constructor always work with "="
> for all the components (with value semantics), i.e., t % p = p1 ?
> ..

You can refer to section 7.5.10 Construction of derived-type values in the standard for details - see paragraph 5:

"For a pointer component, the corresponding component-data-source shall be an allowable data-target or proc-target for such a pointer in a pointer assignment statement (10.2.2). If the component data source is a pointer, the association of the component is that of the pointer; otherwise, the component is pointer associated with the component data source"

spectrum

unread,
Jul 20, 2020, 2:26:07 PM7/20/20
to
On Friday, July 17, 2020 at 4:33:43 AM UTC+9, FortranFan wrote:
> You can refer to section 7.5.10 Construction of derived-type values in the standard for details - see paragraph 5:
>
> "For a pointer component, the corresponding component-data-source shall be an allowable data-target or proc-target for such a pointer in a pointer assignment statement (10.2.2). If the component data source is a pointer, the association of the component is that of the pointer; otherwise, the component is pointer associated with the component data source"

Thanks very much! Though I'm not 100% sure whether I've understood the above
description (from the Standard), it looks like if the actual argument (in the structure
constructor) is a pointer, then the metadata of the latter pointer is copied to that of the
component of the structure. So the overall behavior seems similar to a copy of an object
with pointer components (obj2 = obj1).

# My motivation for this question is that I wanted to add a new element of BaseRef
to "arr", such that

arr = [ arr, BaseRef( p ) ]

which seems to work as expected (though this is costly for an array with many
elements...)

----- test3.F90 (to check the address of components) -----

program main
use iso_c_binding
implicit none

type Base ; endtype
type, extends(Base) :: Type1 ; endtype
type, extends(Base) :: Type2 ; endtype

type BaseRef
class(Base), pointer :: p
endtype
type(BaseRef), allocatable :: refs(:)
class(Base), pointer :: p1, p2, p2_more
integer i

allocate( Type1 :: p1 )
allocate( Type2 :: p2 )
allocate( Type2 :: p2_more )

selecttype ( p1 ); type is (Type1)
print "(a,z0)", "p1(addr) = ", c_loc( p1 )
endselect
selecttype ( p2 ); type is (Type2)
print "(a,z0)", "p2(addr) = ", c_loc( p2 )
endselect
selecttype ( p2_more ); type is (Type2)
print "(a,z0)", "p2_more(addr) = ", c_loc( p2_more )
endselect

refs = [ BaseRef( p1 ), BaseRef( p2 ) ] !! auto-alloc of LHS

!! Add one more variable.
refs = [ refs, BaseRef( p2_more ) ]

do i = 1, size(refs)
print *, "i = ", i

selecttype ( p => refs( i )% p )
type is (Type1) ; print "(a,z0)", "-> Type1: p(addr) = ", c_loc( p )
type is (Type2) ; print "(a,z0)", "-> Type2: p(addr) = ", c_loc( p )
endselect
enddo

end

-----

# Though not related to the question, I strongly feel that the above code illustrates
the tediousness of treating heterogeneous (inhomogeneous) data in Fortran...
Indeed, I feel it like more like a "puzzle" when writing the code (as compared to other
languages, where class variables are references and there is no need to wrap pointers).
Things might change if some new syntax for "array of pointers" may be introduced in
future, but not very sure if it's possible (because many other syntaxes assume
homogeneous / rectangular nature of array elements).

spectrum

unread,
Jul 20, 2020, 2:33:22 PM7/20/20
to
PS. The result of test3.F90 above is the following (which varies
for each run for the address values):

p1(addr) = 7FE388500000
p2(addr) = 7FE388500010
p2_more(addr) = 7FE388500020
i = 1
-> Type1: p(addr) = 7FE388500000
i = 2
-> Type2: p(addr) = 7FE388500010
i = 3
-> Type2: p(addr) = 7FE388500020

Ian Harvey

unread,
Jul 20, 2020, 4:34:47 PM7/20/20
to
On 21/07/2020 3:56 am, spectrum wrote:
> On Friday, July 17, 2020 at 4:33:43 AM UTC+9, FortranFan wrote:
>> You can refer to section 7.5.10 Construction of derived-type values in the standard for details - see paragraph 5:
>>
>> "For a pointer component, the corresponding component-data-source shall be an allowable data-target or proc-target for such a pointer in a pointer assignment statement (10.2.2). If the component data source is a pointer, the association of the component is that of the pointer; otherwise, the component is pointer associated with the component data source"
>
> Thanks very much! Though I'm not 100% sure whether I've understood the above
> description (from the Standard), it looks like if the actual argument (in the structure
> constructor) is a pointer, then the metadata of the latter pointer is copied to that of the
> component of the structure. So the overall behavior seems similar to a copy of an object
> with pointer components (obj2 = obj1).

The description of the behaviour for intrinsic assignment of a derived
type with pointer components references the behaviour of pointer
assignment, which has similar text.
All the example does is print the addresses of the objects. Presumably
that's just for the sake of the example - otherwise you could store
those addresses in an array of integer of suitable kind (and Fortran has
excellent support for integer arrays) and do away with the derived type
wrapper. For more typical cases, there's not that much different
between Fortran and other statically typed languages that I am familiar
with, bar the need for the reference to the intermediate component
("%p") and the general convention of the language to use keywords for
syntax rather than symbols.

Note there is no need to use a pointer component if you want to store
polymorphic objects. Pointers in current Fortran are pretty much only
ever used when you want to reference *something else*, it is not clear
from your short example whether you need that capability or not. If you
were merely using polymorphic values, rather than references, the code
would be a little more concise through elimination of the various
"something else" that your pointers are set to reference.

In Fortran (and other languages), an array is an arrangement of things
that are of the *same type* - the derived type wrapper is then a way of
making things that would otherwise be different types accessible through
the same type (other languages have a different definition of type). If
"heterogeneous" means "different type", then "heterogeneous array" is an
oxymoron in the context of the language (and other languages).

FortranFan

unread,
Jul 20, 2020, 8:02:28 PM7/20/20
to
On Monday, July 20, 2020 at 4:34:47 PM UTC-4, Ian Harvey wrote:

> .. If
> "heterogeneous" means "different type", then "heterogeneous array" is an
> oxymoron in the context of the language (and other languages).

These comments not only come across as senseless, more worryingly they lack any sense whatsoever of *understanding and empathy* with what the earlier poster @spectrum shows an interest in achieving in Fortran code.

c.f. Modern Fortran Explained published (https://www.oxfordscholarship.com/view/10.1093/oso/9780198811893.001.0001/oso-9780198811893) by 3 authors who have extensive experience spanning decades with Fortran standards development. Look up End Matter (aka Appendix) C: "Object-oriented List Example":

* The authors write, "A recurring problem in computing is the need to manipulate a dynamic data structure. This might be a simple homogeneous linked list like the one encountered in Section 2.12, but often a more complex structure is required."

* They then proceed to list an example "for building heterogeneous doubly linked linear lists" by employing a derived type component of unlimited polymorphic type with the POINTER attribute.

What the earlier poster @spectrum shows an interest is far *closer in need* to this example in this de facto "textbook" on modern Fortran that to its credit acknowledges the need being a "recurring problem" in computing.

The fact is for many such needs in modern computing, Fortran is horribly, horribly, horribly deficient compared to all the languages in popular use in engineering and technical computing.

Not only does the WG5 Fortran standard body hurt the language inexorably by indulging in nothing other than deflect, delay, and deny any inclusion of beneficial intrinsic solutions to the problem in the language itself, they take so long - by long, it's decades or more at a time when an entire new, full-featured language with an efficient compiler and libraries for scientific and technical computing can spring up in a couple of years - to bring the basic facilities and utilities that might permit a mere mortal Fortranner to even contemplate a DIY solution that can be seen as half as efficient as what can be consumed "out of the box" using other languages. And all that while paying no heed to trying to make the Fortranner's attempt convenient and fun.

To add to the ongoing SHAME with the world of Fortran, it took the 3 authors close to a decade, mind-numbingly long for someone versed in the Fortran standard, or who should-be given several decades' of experience with the standards work, to get a compilable version of their example and it's *still* not without issues:
https://groups.google.com/d/msg/comp.lang.fortran/aRz3HMpblTs/r4LVdTIV1N8J

It's under these circumstances WG5 and their acolytes deflect the pleas by poor, persisting Fortranners with "that's better suited to a library" response: talk about leaving your practitioners in the lurch.

spectrum

unread,
Jul 21, 2020, 12:09:24 AM7/21/20
to
On Tuesday, July 21, 2020 at 5:34:47 AM UTC+9, Ian Harvey wrote:
> (...snip...)
> In Fortran (and other languages), an array is an arrangement of things
> that are of the *same type* - the derived type wrapper is then a way of
> making things that would otherwise be different types accessible through
> the same type (other languages have a different definition of type). If
> "heterogeneous" means "different type", then "heterogeneous array" is an
> oxymoron in the context of the language (and other languages).

Dear Ian,
Thanks very much for your comment, and just a quick reply about my not-very-precise
sentences (this time, like in a quarter precision ... < half precision)
https://en.wikipedia.org/wiki/Half-precision_floating-point_format
https://en.wikipedia.org/wiki/Minifloat

Searching the net, it seems like the word "heterogenous" is used for simultaneous
use of different kinds of computing devices (like CPU + GPU), so I'm afraid
my use of the words "heterogeneous array" may be very strange or confusing...
https://en.wikipedia.org/wiki/Heterogeneous_computing

I guess "inhomogeneous" seems more appropriate for what I imagine,
but searching the net again, some people use "non-homogeneous" arrays
(not sure whether there is distinction, though).

As for the "same type", yes, I agree that an array is usually a sequence of the same
(declared) type. In my case, it is a sequence of "BaseRef", which occupies
the same length of memory for each element. By "inhomogeneous
(non-homogeneous)", I mean that each element refers to a heap-allocated object
via some metadata contained in the element.
I think the "Base[]" in the D code above is also such an example, but
I am not sure whether my use of the words "inhomogeneous array" is
appropriate for this kind of things... Anyway, what I imagine with this word
is something like that.

I'm also afraid that my words "the tediousness of treating heterogeneous
(inhomogeneous) data in Fortran" may have been a bit offensive (?). Here,
I mean I need more energy (or time) for coding to achieve the same goal.
(I thought "tedious" is sometimes used for describing algorithms etc when
they are not very efficient in some sense, so in a sense similar to that...)

And lastly, I think I've been complaining a lot about Fortran (XD), but IMHO
this is actually good rather than bad, because "There are only two kinds of
languages: the ones people complain about and the ones nobody uses."
according to Mr. LongLegs... :)
https://www.goodreads.com/quotes/226225-there-are-only-two-kinds-of-languages-the-ones-people
0 new messages