Unassociated/unallocated variable interpreted as a non present optional argument is misleading

The feature that makes an unassociated/unallocated variable interpreted as a non present optional argument looks nice (and I have used it on purpose in the past), but recently I encountered a case where it was misleading. Like this:

real, pointer :: a(:)

call foo(a)

contains
   subroutine foo(x)
      real, intent(in), optional :: x(:)
      
      if (present(x)) then
         ! do something
      else 
         ! do something else
      end if
   end subroutine
end

I forgot to associate the pointer a(:) before calling foo(a), and as a result the subroutine was silently doing “something else” instead of the expected “something”… It took me a while to detect the problem, because the code is 100% valid…

Note that replacing the routine with 2 variants (one without the dummy argument and one with the mandatory dummy argument instead of optional) under a generic interface makes the code illegal if a(:) is unassociated/unallocated (and it can be trapped with a runtime check).

1 Like

Hmm… if the actual argument is not allocated or not associated, then the subroutine cannot really do anything valid with it, can it?

If you explicitly state that the dummy argument is a pointer, that’s a different thing:

real, pointer :: a(:)

call foo(a)

contains
   subroutine foo(x)
      real, intent(in), pointer, optional :: x(:)

      if (present(x)) then
         ! do something
          print*,'pointer is present'
      else 
         ! do something else
      end if
   end subroutine
end
$ ifort -diag-disable=10448  optional_ptr.f90 && ./a.out 
 pointer is present

Even with CONTAINed procedures, it sometimes helps to think of them as being separate or provided by a third-party —i.e., it’s not the subroutine’s fault that you forgot to associate a(:).

This code is an over-simplification of my real code :slight_smile: , just to make the point…

Sure, but you don’t necessarily want to restrict the actual argument to a pointer…

I know! My mistake, for sure… But my point is that this feature can be misleading when not used on purpose…

1 Like

I think the issue is that some unintended behavior that’s beyond the reach of the standard, could really benefit from a language server (or a compiler’s --syntax flag in pedantic mode).

The list could include:

  • Passed non-associated/unallocated to non-pointer/non-allocatable optional dummy argument.
  • Reallocated derived type array instance with (now dangling) pointer components.
  • …

This is exploring some of the dark corners of the language. In your posted code, I think the behavior is undefined because the actual argument is in an undefined state. Changing the code to

real, pointer :: a(:) => null()

or adding

nullify(a)

will make it conforming. Of course, your actual code might not have this issue, so maybe this has nothing to do with solving your actual problem.

This is yet another dark corner of the language. The combination of intent(in), pointer is allowed for a dummy argument even if the actual argument is not a pointer. It is a shortcut for declaring the dummy argument as target, declaring a local pointer, and assigning that local pointer to the target.

real, intent(in), target :: dummy(:)
real, pointer :: x(:)
x => dummy

However, I’m unsure what, if anything, the addition of the optional attribute does in this special case. Presumably the programmer can test if present() and pointer assign x(:) to something else? I might even say this is an obscure part of a dark corner.

Yes, I agree with this. It is like implicit save. The semantics of these dark corners of the language is difficult to keep straight. They add only a little functionality to the language (or none at all), but they cause problems when they are used intentionally in an incorrect way or when they are stumbled upon unintentionally. To be honest, I’m not 100% sure that what I said above is correct, and it would take me an hour or so to look through the standard to check all of the cross references to make sure. So let me just say I think what I said above is right, and hopefully someone will correct me if I’m wrong.

2 Likes

It has one nice consequence though. You can write a safe size function like this

pure integer function safe_size(x) result(res)
class(*), dimension(..), intent(in), optional :: x
tes = -1
if (present(x)) res = size(x)
end function

I think also in this case the actual argument must be in a defined state in order for present() to work correctly on the dummy argument.