Type bound procedure returning a pointer

Hey Folks,

I want to double-check an assumption I have for a type bound procedure function that returns a pointer, pointing to an attribute of the type.

Assume we have this type holding a concatenated array:

module array_test
  type :: array_concat
    integer, allocatable :: arrays(:)
    integer, allocatable :: offset(:)
    integer, allocatable :: length(:)
  contains
    procedure :: view
  end type array_concat
contains
  function view(this, index) result(array)
    class(array_concat), target, intent(in) :: this ! note target attribute
    integer, intent(in) :: index
    integer, pointer :: array(:)
    integer :: first, last
    first = this%offset(index)
    last = first + this%length(index) - 1
    array => this%arrays(first:last)
  end function view
end module array_test

I know that within view it is valid to put a target attribute on the class dummy argument and then point to attributes of this.

In order for the pointer to be valid after the function return, the type instance needs to be a valid target, like this:

program array_prog
  use array_test, only: array_concat
  implicit none
  type(array_concat), target :: ac
  integer, pointer :: arr(:)
  ac%arrays = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  ac%offset = [1, 4, 7]
  ac%length = [3, 3, 4]
  ! point to the second sub-array (elements 4, 5, 6)
  arr => ac%view(2)
end program array_prog

Now my question:
When the instance is not a valid target, I think, I can still use view to get a copy of the desired array with this:

program array_prog
  use array_test, only: array_concat
  implicit none
  type(array_concat) :: ac ! no target
  integer, allocatalbe :: arr(:) ! allocatable instead of pointer
  ac%arrays = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  ac%offset = [1, 4, 7]
  ac%length = [3, 3, 4]
  ! COPY the second sub-array (elements 4, 5, 6)
  arr = ac%view(2) ! implicit allocation
end program array_prog

The function should allocate the LHS arr before the returned pointer get disassociated (I know some compilers would still allow a pointer to be valid after return (arr => ac%view(2)), but by the standard, ac is not a target, so I can’t / shouldn’t point to attributes of it).

Is this right? If so, this would be great to have a dual-use of the view function (either associating a pointer of creating a copy).

Cheers,
Sebastian

If I’m understanding your code correctly, I don’t think there is any guarantee for this. Without the target attribute on the actual argument, a temporary copy can be created for the dummy argument, say on the stack. The pointer assignment, array=>, would then point to that temporary copy, not to the actual argument array. The stack could be popped or overwritten at any time after the return, including before the left-hand-side assignment of the allocatable array. It is the target attribute on the actual argument that prevents all that from happening. On the other hand, none of that is required to happen, the allocatable assignment could reference the actual argument array even without the target attribute, and you code would have a hidden bug just waiting to explode in your face with a new version of your compiler, or with a different compiler, or with different compiler options, etc. Also, I don’t think the type bound procedure part is relevant, this all occurs with argument association of regular procedures too.

I think I disagree. ac%view(2) is a function result, that happens to be a POINTER. It is used in a reference context, not in a variable definition context, which means its value is required. Its value is clearly the value of the target that it was associated with during execution. That value, a rank-one array with a size and values, will be used to reallocate the LHS, if necessary, and any elements of the LHS will be defined by the supplied values. The mission of the function result having been accomplished, it will cease to exist.

I think you are saying that the size and rank metadata should be preserved correctly by the pointer, but that the values are undefined without the target attribute. This case seems complicated, but I think I agree with that part. But the standard does not allow assignments to undefined values, right? If you are saying that the values should also be preserved, without the target attribute on the actual argument, then I do not agree. The subroutine itself doesn’t know if its dummy argument is a copy, so it cannot know if the pointer assignment it executes points to data that survives the return statement. This is an interesting question.

As pointed out in Is pointing to a target dummy argument safe after return?, the Fortran Standard says:

When execution of a procedure completes, any pointer that remains defined and that is associated with a dummy argument that has the TARGET attribute and is either a scalar or an assumed-shape array, remains associated with the corresponding actual argument if the actual argument has the TARGET attribute and is not an array section with a vector subscript.

The question now is: Is a function call counted “complete” before or after the LHS was set (in this case with intrinsic allocation).

If it completes after, the copy should be fine since the pointer it was copied from was still associated to the desired target. If not, the copy case doesn’t work.

I tested it with several compilers (gfortran, ifort, ifx, nag) and they all create a correct copy.

But I guess I should avoid this and implement two separate TBP (preferably subroutines): one for a view, one for a copy.
Pointer returning functions were discussed a lot already and they seem to have to many pitfalls.

Unfortunately one can’t put a pointer attribute on the passed-object dummy, to enforce the instance to be a valid target, since this is forbidden by the standard (That is the special difference between TBP and standalone procedures).

In the code you posted, there was a single function reference on the rhs, so one might imagine that the pointer values might happen to still point to the values as the assignment occurs. Consider a more complicated situation that involve several function references. Without the target attribute on the actual argument, each of those functions could have dummy arguments associated with copies of their actual arguments, and the stack memory that is used for those argument associations could be reused as the individual functions are called, one by one, in order to evaluate the rhs of the expression. The underlying values that are pointed to in one function would be changed (undefined in standardspeak) as the other functions are invoked.

This is the part where I think it is complicated, where the actual argument does not have the target (or pointer) attribute.

No, I am not saying that. I am saying that the function result is used in a context (“reference”) where the values are needed (including attributes such as shape and type parameters). It is the responsibility of the compiler to make sure that those values are delivered to whatever constitutes the “consumer” (here, the RHS of a reallocating assignment). If there are seventeen function results needed to assemble the RHS, then this will need to happen seventeen times.

They are not “preserved” because there is no danger of the function result being referenced again. But that does not mean that the values cannot be delivered to the context. If a copy has been created at some point in the execution of the statement, all the compiler has to do is delay the deletion of the copy until the whole statement has been processed (and everything that needed redefining has been redefined).

Function results should be thought of separately from redefinable actual arguments. Fortran already has

if a function reference causes definition or undefinition of an actual argument 
of the function, that argument or any associated entities shall not appear 
elsewhere in the same statement.

to keep things sane. The function result, by its syntactic nature, is an anonymous “hapax legomenon” and so is already safe from such shenanigans.

I’m still not convinced. Yes, the function result values are apparently “needed” by the programmer, but those values are not (I think) defined by the standard. Here is a little toy program that demonstrates what I think can happen.

program pointerf
   implicit none
   integer, target :: a(2) = [1,2]
   integer, allocatable :: b(:)
   b = fpt(a,1)
   write(*,*) 'b=', b
contains
   function fpt(c, offset)
      integer, pointer :: fpt(:)
      integer, intent(in) :: c(:), offset
      integer, target :: d(size(c))
      d = c + offset
      fpt => d
      print *, 'fpt=', fpt
      return
   end function fpt
end program pointerf

$ gfortran pointerf.f90 && a.out
 fpt=           2           3
 b=           0           0

Within the function, the pointer is assigned to an automatic array, which is presumably on the stack. The values are defined within the function, but that array disappears upon the function return, so the values pointed to are undefined upon return. Here, it appears that they are overwritten with zeros, but that detail is irrelevant (another compiler might print different results). The important part as far as language semantics is that the values are undefined. The array metadata, namely the rank and size, are retained by the function, but the underlying values have evaporated. Thus in the assignment, the array b(:) has the correct shape, but the values are undefined.

This is not exactly the same as the original type bound example, but it demonstrates the situation that I think can still happen in that case. Namely, a temporary array can be created (by copy-in/copy-out argument association) and filled with valid values, a pointer is assigned to that temporary array, the underlying array is undefined upon return (the stack memory can be overwritten), and that pointer is used in the context of an allocatable array assignment.

I think that you are right, in the end.

The current situation makes pointer functions a foot-gun, but we can’t get away from 19.5.2.5 p1 (10)

The association status of a pointer becomes undefined when
...
(10) execution of an instance of a subprogram completes, the pointer is 
associated with a dummy argument of the procedure, and 
(a) the effective argument does not have the TARGET attribute or is an
array section with a vector subscript, or
(b) the dummy argument has the VALUE attribute,

In your last case, it was a different subclause that activated

(6) completion of execution of an instance of a subprogram causes the 
pointer’s target to become undefined (item (3) of 19.6.6),

Perhaps a future revision will change the situation.

1 Like