question about move_alloc and allocatable function results

question about move_alloc and allocatable function results

Izaak Beekman's picture

Hi, I can't seem to tell from the ifort documentation, and other vendors fortran compiler documentation whether a 'from=my_allocatable_function(intent_in_args)' would be allowed in move_alloc assuming a type-compatible allocatable variable is passed via the to dummy argument.

 call move_alloc(from=my_allocatable_function(intent_in_args),to=type_compatible_allocatable_variable) 

The goal of this would be to have the variable take on the values and memory of the function result to make it persistent (until the variable is deallocated) to allow any pointers to the function result (say a local pointer within the function with the save attribute) to now point to the variable (assuming it is a pointer or has the target attribute) and perhaps most importantly, to avoid copying a large derived type.

Is this usage standards conforming? Should one expect it to work?

TIA

-Zaak
10 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.
IanH's picture

No.  A function reference in this context (it is not a pointer function nor a pointer argument) is part of an expression, the result of evaluating an expression is a value (versus a variable), values cannot be allocatable (though they can have components that are allocatable) so they ain't no good for the FROM argument of MOVE_ALLOC.

Perhaps an allocatable + target actual and dummy argument for a subroutine call might fit the bill.

Steve Lionel (Intel)'s picture

What an interesting question!  If I chase down the current standards wording, which leads me on a treasure hunt across multiple chapters, I initially conclude that, no, this is not permitted. Here's the logic I followed.

First, the FROM argument to MOVE_ALLOC is defined as having the INTENT(INOUT) attribute.

INTENT(INOUT) says "The INTENT (INOUT) attribute for a nonpointer dummy argument specifies that any actual argument that corresponds to the dummy argument shall be definable."

Looking up "definable", I see that only data objects can be definable.

A data object is "constant (4.1.3), variable (6), or subobject of a constant (2.4.3.2.3)"

Of these, only "variable" might qualify, so let's look there. R602 says that a variable is either a designator or an expr (expression). The function reference is an expr, not a designator.

Finally, C602 says "expr shall be a reference to a function that has a data pointer result." which is not the case here. Therefore, the allocatable function result doesn't qualify as a variable, isn't definable, and is excluded from INTENT(INOUT), thus not allowed as the FROM in a call to MOVE_ALLOC.  Whew!

F2008 does allow pointer-valued function references on the left side of an assignment. (Intel Fortran doesn't support this yet), but allocatable functions are not allowed here. So I think the standard is consistent, though one might be excused for wondering if that distinction isn't artificial. On the other hand, you can do pointer assignment to a pointer function result, whereas all you can do with an allocatable function result is use its value - and it then gets deallocated automatically at the end of the statement.

I notice that Intel Fortran doesn't allow the MOVE_ALLOC call, complaining that the FROM argument needs to be allocatable. I think one could also argue that while the function result has the allocatable attribute, the function reference does not, since there is no language rule that leads to this combination.

Steve
Ferdinand T.'s picture

Oh, thank you for clearifying this!

May I extend the question to whether or not a sourced allocation (legally) performs the task Izaak wants to achieve (e.g. "moving the allocation" instead of copying the value from the function result)?


	allocate(type_compatible_allocatable_variable, source = my_allocatable_function(intent_in_args))

	

I remember that the Intel documents say the source expression must not be allocated in the same allocate statement. Does this make sourced allocation illegal here, too?

Regards, Ferdinand

Izaak Beekman's picture

Ian and Steve, thanks so much for your replies.

Ian, I think I will have to use allocatable dummy arguments to a subroutine, as you suggest. I can't really come up with a better alternative.

The problem, is that I'm trying to hide some implementation and object creation details behind a factory pattern that will return the instantiated object cast as its abstract parent class, and optionally aggregate the new object into two already existing objects via pointer lists in those objects. Rouson et al's approach is syntactically elegant, but I don't want to deal with the headache of memory management that pointer variables introduce. The challenge is to create the new object, point at it optionally from the objects which aggregate it, and then return from the procedure with the pointer references in tact and pointing to the variable which is returned to the parent scope.

One option is just have the factory method be a subroutine as Ian suggested and then the client code can pass it a class(abstract_class_to_be_instantiated) ,allocatable, target variable. The factory method can then allocate the dummy, allocatable, target intent(out) variable to the desired concrete type and tell the other (optional) objects to aggregate/point at it. The pointers will then stay associated with the variable upon return from the subroutine since the dummy and actual arguments have the target attribute.

A, perhaps ill-advised, way to try to mimic the functional style of Rouson et al would be to use a function rather than a subroutine. There would still be an intent(out) (or inout) allocatable, target dummy argument to handle the memory allocation of the object being created. The function result could then be a pointer, and point to the dummy argument it just allocated memory for:

 foo = concrete_foo_factory%create(foo,a,b) 

where:

function create(this,foo,a,b) result(res)
class(foo_factory) ,intent(in) :: this
class(abstract_foo) ,allocatable ,intent(out) ,target :: foo
class(aggregator) ,intent(inout) :: a ,b
class(abstract_foo) ,pointer :: res
allocate(foo_concrete :: foo)
a%list%u => foo
b%list%v => foo
res => foo

Obviously this function has side effects, and, if realloc_lhs is turned on, it is possible that the assignment will erroneously reallocate foo, leaving dangling pointers in a and b to where foo used to be. Further more, foo appearing in the argument list and the assignment is quite redundant. I think this is probably overly complicated and error prone.

Note that we can't simply use an overloaded structure constructor of the original type, because this must be a function, and the result variable will be assigned (or sourced allocated) to the LHS, so any pointers pointing to it will become dangling pointers. A TBP can't be used, because the object doesn't exist yet, so there's not way to call a TBP on an unallocated object.

Any way, thanks for the help guys. I'll just KISS and use Ian's suggestion.

-Zaak
Steve Lionel (Intel)'s picture

In the sourced allocation case, there will be a new allocation and the data copied. There's no conflict.

Steve
Izaak Beekman's picture

Steve,

I'm not sure my wording was terribly clear.

I think any pointer assignments done inside the structure constructor style instantiator where a dummy (intent(inout)) object is pointer assigned *to the function result* will become dangling pointers as soon as the function goes out of scope. The pointers aren't components of the object being created, but of other objects linking to the object being created. The new object and all of it's data will be correct/copied etc after the sourced allocation, but any pointer assignments to the function result will not get updated to the location of the new object, if I understand things correctly.

interface my_type
procedure :: create_my_type
end interface
function create_my_type(a,b) result(res)
class(aggregator) ,intent(inout) :: a ,b
type(my_type) ,target :: res
a%list%u => res
b%list%v => res
end function
! from main:
class(abstract_my_type) ,allocatable, target :: foo
!Instantiate etc. a, b
allocate(foo,source=my_type(a,b))
!a%list%u and b%list%v is pointing to the function result of my_type which is now out of scope

-Zaak
Steve Lionel (Intel)'s picture

You're correct that any pointers in the result used in SOURCE will be dangling if used this way.

Steve
Ferdinand T.'s picture

Quote:

Steve Lionel (Intel) wrote:

In the sourced allocation case, there will be a new allocation and the data copied. There's no conflict.

Is there no hope for preventing the copy, even with optimizations turned on? In some testcases, a "print"ed notification in a finalizer of the allocatable function result (derived type) was not displayed in sourced allocation, so I guessed that only references had been copied.

Thanks, Ferdinand

Steve Lionel (Intel)'s picture

No - the language doesn't allow that.
 

Steve

Login to leave a comment.