It’s very common when dealing with optional arguments to have a local variable that is assigned either a default value or the value of a present argument, like:
subroutine bar(...,a)
real, intent(in), optional :: a
real :: a_local
a_local = 2.0; if (present(a)) a_local = a
... ! use only a_local in the further code
end subroutine
And it’s also very common to use the argument a by mistake instead of a_local, resulting in a segmentation violation during execution if a was not present.
A way to prevent such an error is to use an ASSOCIATE block that spans the whole routine:
subroutine bar(...,a)
real, intent(in), optional :: a
real :: a_local
a_local = 2.0; if (present(a)) a_local = a
ASSOCIATE( a => a_local )
... ! "a" can now be safely used as an alias to a_local
END ASSOCIATE
end subroutine
The dummy argument a is shadowed by the alias a inside the block. Another benefit is that we don’t have to use the local variable name with a somehow artificial suffix (_local or whatever)
In theory, you can have as many nested associate and block constructs as you want.
In practice:
The associate-construct is a thin pseudo-scope (the underlying behavior is that of pointers and shadowing), so you could nest as many as you want… until it starts getting confusing.
The block-construct, on the other hand, is a true scope, so it’s bound by the stack’s limits (and recursion complicates things).
Both constructs are also bound by the compiler’s rules (e.g., for ifort, I think the magic nesting number was 31, all constructs combined).
The two approaches are actually complementary. optval() is fine as long as the argument is used once or twice. If repeatedly used it will tend to be less readable than a local variable (and a change of the default value requires several changes in the code).
They can also be combined (not tested, but should work):
ASSOCIATE blocks work, more or less, like invoking a contained procedure with actual arguments being associated with the contained procedure dummy arguments. However, one difference is that ASSOCIATE blocks are allowed to be nested arbitrarily by the standard (at least to some compiler limit), while CONTAINS is limited to only one level (or two, if you count the CONTAINS statement in a module). A programmer runs into that CONTAINS limit when converting statement functions in legacy codes to contained procedures within a module. That limit has always annoyed me, I guess for being so arbitrary and seemingly unnecessary. You can usually work around the problem by flattening the hierarchy, but it sometimes requires renaming the functions to avoid conflicts.
My Pure-Fortran project has a script to warn about the unsafe use of optional arguments. For the code
module m
implicit none
contains
function max2(x1, x2) result(xmax)
real, intent(in) :: x1
real, intent(in), optional :: x2
real :: xmax
if (present(x2)) then
xmax = max(x1, x2)
else
xmax = x1
end if
end function max2
function max_(x1, x2, x3, x4, x5) result(xmax)
real, intent(in) :: x1, x2
real, optional, intent(in) :: x3, x4, x5
real :: xmax
xmax = max(x1, x2)
if (present(x3)) xmax = max(x3, xmax)
xmax = max2(xmax, x4) ! allowed since 2nd argument of max2 is OPTIONAL
xmax = max(xmax, x5) ! unsafe since x5 may not be PRESENT
end function max_
end module m
use m
print*,max_(3.2, 2.6)
print*,max_(3.2, 2.6, 4.5)
end
python xoptional.py xoptional.f90 gives
1 optional-argument guard finding(s) in 1 file(s) (definite 1, possible 0).
xoptional.f90: 1
First finding: xoptional.f90:22 function max_ x5 [definite] - optional argument may be used without guaranteed PRESENT() guard