I need all the elements of iuse(:), which will be used as a vector subscript for xmat and yvec, to be between 1 and size(yvec), or there will be an out-of-bounds error. How you do decide which way to handle this:
(1) Do nothing. Let the program crash for invalid iuse.
(2) Check that iuse is valid upon entering foo and
(a) print an error message and STOP
(b) print an error message and RETURN
(c) set an error flag argument ierr and RETURN
(d) do either (a) or (c) depending on whether optional error flag ierr is passed.
(3) Check the input and fix it if invalid. Define an array iuse_(:) = pack(iuse,iuse>0 .and. iuse <= n) and use iuse_ as the vector subscript.
Since it’s an individual project I will probably do 2(a), which forces the calling code to be corrected.
I’m pretty new to Fortran so take my advice with a grain of salt, but I’m a fan of a hybrid approach between 2c and 2a… have an OPTIONAL error flag argument. If it’s provided, set it when an error ocurrs. If the flag argument is not provided just do stop with an error message.
I like this approach because the caller can decide if they want to handle errors or if they want them to be fatal.
Edit: I just realized that’s exactly what you’re saying in 2d… So I vote for 2d!
I lean towards 2c, but I would say 2d is pretty typical and acceptable, provided you use error stop instead of stop.
@pmk, alternate return scares me. But I have seen it suggested that it could be used as a poor man’s emulation of exception handling, which intrigues me.
(4) Consider a parameterized derived type “container” toward matrices and associated vectors, that provides a surefire and efficient method for bounds matching. Intel oneAPI IFORT is now quite functional here, so appears to be NAG Fortran 7.0.
..
type :: mat_t(n,m) ! may be just 'n' for square matrices
integer, len :: n, m
real :: xmat(n,m)
real :: yvec(n)
integer :: iuse(n)
..
contains
subroutine foo( this, luse )
type(mat_t(n=*,m=*)), intent(inout) :: this
logical, intent(in), optional :: luse
! No bounds checking necessary here
if ( present(luse) ) then
if ( luse ) then ! use this%iuse in the instructions
..
For functions I think the two main approaches are ERROR STOP or returning an obviously bad RESULT, that one hopes the calling program will catch, for example
pure function correl(x,y) result(corr)
! use ERROR STOP
real, intent(in) :: x(:),y(:)
real :: corr
if (size(x) < 2) then
error stop "in correl, need size(x) > 1"
else if (size(x) /= size(y)) then
error stop "in correl, need size(x) == size(y)"
end if
! calculate corr
end function correl
!
function correl_old(x,y) result(corr)
! use STOP if compiler does not support error stop
real, intent(in) :: x(:),y(:)
real :: corr
if (size(x) < 2) then
print*,"in correl, need size(x) > 1"
stop
else if (size(x) /= size(y)) then
print*,"in correl, need size(x) == size(y)"
stop
end if
! calculate corr
end function correl_old
!
pure function correl_f95(x,y) result(corr)
! return clearly bad result for invalid input
real, intent(in) :: x(:),y(:)
real :: corr
if (size(x) < 2) then
corr = -2.0
return
else if (size(x) /= size(y)) then
corr = -3.0
return
end if
! calculate corr
end function correl_f95
Definitely 1 for high performance applications, if the subroutine is called many times. If it is a high level subroutine, then you can put the check there. Always enable bounds checking in Debug mode and always check your final application in Debug mode on actual input (test) data to ensure everything works. Then run in Release mode for production runs.
I generally do not like to use pre-processing, but using ASSERT macros (enabled in Debug mode, but no-op in Release mode) can also be used for this purpose.