Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Optional arguments in subroutines

OP1
New Contributor II
1,111 Views
Let's consider the following subroutine:

SUBROUTINE MAIN_SUB(A,B)
USE INTERFACE_FOR_OTHER_SUB (module containing the interface definition for OTHER_SUB).
IMPLICIT NONE
INTEGER,INTENT(INOUT) :: A
INTEGER,INTENT(IN),OPTIONAL :: B
...
Assume that MAIN_SUB calls another subroutine for which the argument Bis also optional. I suppose I need to do the following:
...
IF (PRESENT(B)) THEN
CALL OTHER_SUB(A,B)
ELSE
CALL OTHER_SUB(A)
ENDIF
...
END SUBROUTINE MAIN_SUB

In other words, OTHER_SUB is a subroutine which arguments are exactly as provided by MAIN_SUB.

The branching statement above works well when there is one optional argument. But what if my subroutine has, say, 4 or 5? It becomes a combinatorial problem to implement all the possible present/non present combinations for all optional arguments!
Am I doing something more complicated than necessary? Maybe there is another solution?

Thanks,

Olivier
0 Kudos
8 Replies
Les_Neilson
Valued Contributor II
1,111 Views
Quoting - opmkl
Let's consider the following subroutine:

SUBROUTINE MAIN_SUB(A,B)
USE INTERFACE_FOR_OTHER_SUB (module containing the interface definition for OTHER_SUB).
IMPLICIT NONE
INTEGER,INTENT(INOUT) :: A
INTEGER,INTENT(IN),OPTIONAL :: B
...
Assume that MAIN_SUB calls another subroutine for which the argument Bis also optional. I suppose I need to do the following:
...
IF (PRESENT(B)) THEN
CALL OTHER_SUB(A,B)
ELSE
CALL OTHER_SUB(A)
ENDIF
...
END SUBROUTINE MAIN_SUB

In other words, OTHER_SUB is a subroutine which arguments are exactly as provided by MAIN_SUB.

The branching statement above works well when there is one optional argument. But what if my subroutine has, say, 4 or 5? It becomes a combinatorial problem to implement all the possible present/non present combinations for all optional arguments!
Am I doing something more complicated than necessary? Maybe there is another solution?

Thanks,

Olivier

Unless you can make use ofdefault values. Something like :

integer :: local_b, local_c
local_b = 42
local_c = 9
if (present(b)) local_b = b
if (present(c)) local_c = c
call other_sub(a,local_b,local_c)

etc
Otherwise you're stuck with the multiple if/then tests

Les
0 Kudos
Greg_T_
Valued Contributor I
1,111 Views
Hello Olivier,

To avoid the many combinations of variables in the other_sub() call statement, perhaps you could use one or two small arrays? When I have a routine with many possible variables being passed, I will put the values into a small array and then pass that array to the other_sub(). Another possibility is to use a derived data type that contains all the variable values that you would like to pass. I find both the approaches very helpful when over time more variables are added. The array or data type can be easily expanded, and no changes to the call statement are needed, which is very convenient.

For example using a few small arrays:

integer, parameter :: array_size = 10 ! set the size you need
integer :: values_int(array_size)
real :: values_real(array_size)

! then set your variables into the small arrays
values_int(1) = A
...
values_real(1) = B
...

! then call the subroutine
call other_sub(array_size,values_int,values_real)

Or for example with a derived data type:
! declare this in your routine, or in a module
type my_type
integer :: a
integer :: b
real :: c
end type

! declare a variable of my_type in your routine
my_type :: values

values%a = A
values%b = B
values%c = C

! then call your subroutine
call other_sub(values)

Would either of these approaches help with the many variable cases needed? I think the syntax to set up either approach is easier than a big if-block, and can be easily expanded without needing to change the call statement.

Regards,
Greg

0 Kudos
OP1
New Contributor II
1,111 Views
Thanks to all for such detailed answers!! Your suggestions gave me the following idea: create a derived type in which all the optional arguments are listedalong a logical flag (indicating that the derived type component variables are active/present)is provided for each argument.

TYPE MY_OPTIONAL_ARGS
INTEGER A,B,C,D
LOGICAL A_IS_PRESENT,B_IS_PRESENT,C_IS_PRESENT,D_IS_PRESENT
END TYPE MY_OPTIONAL_ARGS

But this kind of defeats the very purpose of optional arguments of course.
Well... as long as it works and gets the job done...

Thanks again,

Olivier
0 Kudos
jimdempseyatthecove
Honored Contributor III
1,111 Views

Olivier,

I believe that an incomming optional argument(s) can be passed as optional argumentsin a call to a nested subroutine as long as it is (they are) last in the argument list of the nested subroutine. This should be easy enough for you to test.

Have you looked at using generic interfaces? Depending on the amount of common code you might find it best to write two subroutines with one generic name.

Jim Dempsey
0 Kudos
IanH
Honored Contributor II
1,111 Views

I believe that an incomming optional argument(s) can be passed as optional argumentsin a call to a nested subroutine as long as it is (they are) last in the argument list of the nested subroutine.

Order in the nested subroutine argument list doesn't matter, the 'not present'ness of the original argument is passed along regardless, as long as the receiving dummy arguments are also OPTIONAL (12.4.1.6 in F2003, and it is in a Dr Fortran article on optional arguments too).

[cpp]! $Id:$
! Test of optionality

MODULE AnOptionalTest
  IMPLICIT NONE
CONTAINS
  SUBROUTINE TopLevel(top_arg1, top_arg2, top_arg3)
    CHARACTER(*), OPTIONAL :: top_arg1, top_arg2, top_arg3
    !****
    ! Just pass through
    CALL BottomLevel(top_arg1, top_arg2, top_arg3)
  END SUBROUTINE TopLevel

  SUBROUTINE BottomLevel(bot_arg1, bot_arg2, bot_arg3)
    CHARACTER(*), OPTIONAL :: bot_arg1, bot_arg2, bot_arg3
    !****
    CALL AmIThere(bot_arg1, 'bot_arg1')
    CALL AmIThere(bot_arg2, 'bot_arg2')
    CALL AmIThere(bot_arg3, 'bot_arg3')
  END SUBROUTINE BottomLevel

  SUBROUTINE AmIThere(arg, str)
    CHARACTER(*) :: str
    CHARACTER(*), OPTIONAL :: arg
    !****
    IF (PRESENT(arg)) THEN
      WRITE (*,100) str, arg
    ELSE
      WRITE (*,110) str
    END IF
    100 FORMAT(A,' is there with value ',A)
    110 FORMAT(A,' is not present')
  END SUBROUTINE AmIThere
END MODULE AnOptionalTest

PROGRAM ItsAllOptional
  USE AnOptionalTest
  IMPLICIT NONE
  !****
  CALL TopLevel(top_arg2='Hi there')
END PROGRAM ItsAllOptional[/cpp]


0 Kudos
Steven_L_Intel1
Employee
1,111 Views
Good advice all around. Just don't forget that an explicit interface is required to be visible to the caller if the routine has an OPTIONAL argument. Here's the link to Doctor Fortran and the Virtues of Omission.
0 Kudos
OP1
New Contributor II
1,111 Views
Oooh, this is perfect."Presence Optionality" (for lack of a better term)of an argumentis passed to subroutines, as long as there is an explicit interface for these subroutines.
That's going to save me a lot of headaches...

Thank you!!

Olivier
0 Kudos
GVautier
New Contributor II
1,111 Views
As far as I know, I guess that missing optional argument pointer are set to 0 by the caller and the IS_PRESENT function only test if the pointer value is 0 or not.

So if it is true, optional arguments can be transfered automatically to other subroutine with optional arguments.


0 Kudos
Reply