0

I would like to write a procedure which takes an optional argument, which may be of type FooType or of type BarType, such that this program is valid:

module m
  implicit none
  
  type :: FooType
  end type
  
  type :: BarType
  end type
end module

program mwe
  use m
  implicit none
  
  type(FooType), allocatable :: foo(:)
  type(BarType), allocatable :: bar(:)
  
  call func()
  call func([FooType()])
  call func([BarType()])
  call func(foo)
  call func(bar)
end program

My current attempt is:

module m
  implicit none
  
  type :: FooType
  end type
  
  type :: BarType
  end type
  
  interface func
    module procedure func_FooType
    module procedure func_BarType
  end interface
contains

subroutine func_FooType(input)
  type(FooType), intent(in), optional :: input(:)
  write(*,*) 'foo'
end subroutine

subroutine func_BarType(input)
  type(BarType), intent(in) :: input(:)
  write(*,*) 'bar'
end subroutine
end module

But this does not work. Compiling with either gfortran 10.1.0 or ifort 2021.1 gives the output

foo
foo
bar
foo
bar

The problem is the final call, to func(bar). bar is not allocated, and so if it were passed to an optional argument it would not be present. However, the procedure which gets called is func_BarType, which believes its input is not optional, and so has no way of checking if input is present. Indeed, if I change the input to be a scalar not an array, the call to func(bar) crashes at runtime under gfortran.

veryreverie
  • 2,871
  • 2
  • 13
  • 26
  • Is allocating to zero size an acceptable alternative? – Ian Bush Feb 10 '21 at 18:30
  • I don't think so. My current use case is string parsing, where e.g. an empty string might represent a blank line, whereas no argument would mean no line at all. Also, I have cases where the argument is not an array. – veryreverie Feb 10 '21 at 18:43
  • It might be possible to have a value representing "ignore this argument", but that seems like an unpleasant solution. – veryreverie Feb 10 '21 at 18:44
  • "The problem is the final call, to func(bar). bar is not allocated, so input is not present". That's not true. `input` is not optional in `func_BarType`, so it _is_ present. The actual argument `bar` not allocated makes it an illegal subroutine call. – francescalus Feb 10 '21 at 19:28
  • Generic resolution happens before the test for not-allocated-means-not-present. We don't say "our argument is not allocated, so it isn't present, so that resolves to this specific" but "our argument is of this type so we resolve to this specific. And if it's an optional non-allocatable dummy, is the actual argument allocated?". – francescalus Feb 10 '21 at 19:35
  • @francescalus True. I guess I'm trying to say "If the argument to `func_BarType` were `optional` then it would not be `present`." But it's not possible to make the argument `optional`. I'll edit my question. – veryreverie Feb 10 '21 at 21:52
  • @francescalus What I'm trying to do is define the subroutines such that all the calls from the main program are legal. – veryreverie Feb 10 '21 at 21:56
  • At the very least if you want the actual argument to be allocatable and potentially unallocated the dummy argument will have to be allocatable. You can never pass an unallocated allocatable to a dummy argument which does NOT have the allocatable attribute. See https://stackoverflow.com/questions/13496510/is-there-anything-wrong-with-passing-an-unallocated-array-to-a-routine-without-a – Ian Bush Feb 11 '21 at 08:28
  • 1
    @IanBush, the actual can be not allocated when the dummy is an optional non-allocatable. – francescalus Feb 11 '21 at 08:58
  • Ahh, true. I try to forget about this as I like to sleep at night. – Ian Bush Feb 11 '21 at 17:17
  • @IanBush Interesting. I can see why you dislike the feature, but I can't think of a better way of solving the problem of converting a file full of optional inputs into the arguments for a function. At present, I simply leave the inputs which aren't in the file unallocated. Which leads me to my current problem... – veryreverie Feb 12 '21 at 09:53

1 Answers1

1

I think the only way to do this is with a single function which takes an optional polymorphic argument, so:

subroutine func(input)
  class(*), intent(in), optional :: input(:)
  
  if (present(input)) then
    select type(input); type is(FooType)
      ! Code for FooType.
    type is(BarType)
      ! Code for BarType.
    class default
      ! Any other class. Probably throw an error.
    end select
  else
    ! Code for input not present.
  endif
end subroutine

I'm surprised that there doesn't seem to be a way of doing this without using polymorphic types.

veryreverie
  • 2,871
  • 2
  • 13
  • 26