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

Need generic subroutine with array or simple argument

dboggs
New Contributor I
499 Views

I need to develop a general library subroutine that can be called with either a simple or an array argument, similar to the way generic intrinsic functions can be called with either real or integer arguments.

The subroutine SUB has access to a list of values, and calls to SUB may request either a single value, or an array of values.

In Fortran 77 this was easy, as in the following code:

PROGRAM TRYIT
REAL X
REAL XA (5)
! Call the routine with a simple argument to get a single value
CALL SUB (X)
PRINT *, 'Returned value X =', X
! Call the routine with an array argument to get multiple values up to 5
CALL SUB (XA)
PRINT *, 'Returned values XA =', XA
END

SUBROUTINE SUB (GENERAL)
DIMENSION GENERAL (100) ! Library capacity is up to 100 values
N_NOW = 3
DO I = 1, N_NOW
GENERAL(I) = 10. * I
END DO
END

Program output:
Returned value X = 10.000
Returned value XA = 10.000 20.000 30.000 0.000 0.000

Which is exactly what I want. This worked because the compiler did not enforce type correspondence for arguments. So, the simple real argument Xacted as a pointer to the first element of the dummy argument array. I believe this was a quite common and widely used feature.

In Fortran 90, or at least my configuration of the IVF compiler, this feature is not allowed. Is there some more modern, formal way of accomplishing the same thing? Possibly involving pointers or interface blocks (which I know little about)?

Thanks to anyone whocan give me a concise sample code
.

0 Kudos
9 Replies
dboggs
New Contributor I
499 Views
OK I have just semi-answered my own question (no better way to learn something than to teach it to others?) I can accomplish it using optional arguments. I just write the subroutine like this:

SUBROUTINE SUB (A_1, A_MULT)
REAL, OPTIONAL :: A_1
REAL, OPTIONAL :: A_MULT(100)

IF (PRESENT (A_1)) THEN
...
! Return the single value
...
IF (PRESENT (A_MULT)) THEN
...
! Return the array of values
...
ENDIF

END

This seems like a good solution to me, but I'd still be interesed in other ways.
0 Kudos
IanH
Honored Contributor II
499 Views
(Passing a scalar to a function expecting an array was an error in F77, but one that often (and still) "worked" without compiler smarts to detect it, due to the typical calling convention. However this is processor specific, and hence somewhat dangerous to rely on.)

Maybe you want a generic interface?

[fortran]MODULE my_library_routines
  IMPLICIT NONE
  PRIVATE
  PUBLIC :: sub
  INTERFACE sub
    MODULE PROCEDURE sub_scalar
    MODULE PROCEDURE sub_array
  END INTERFACE sub
CONTAINS
  SUBROUTINE sub_scalar(a)
    REAL, INTENT(OUT) :: a
    !****
    a = 99.9
  END SUBROUTINE sub_scalar
  SUBROUTINE sub_array(a)
    REAL, INTENT(OUT) :: a(:)
    !----
    INTEGER :: i
    !****
    DO i = 1, SIZE(a) ; a(i) = REAL(i) ; END DO
  END SUBROUTINE sub_array
END MODULE my_library_routines

PROGRAM test_it
  USE my_library_routines
  IMPLICIT NONE
  REAL :: a_scalar
  REAL :: an_array(10)
  !****
  CALL sub(a_scalar)
  PRINT "('a scalar:',F10.1)", a_scalar
  CALL sub(an_array)  
  PRINT "('an_array:',999(F10.1,:,1X))", an_array  
END PROGRAM test_it[/fortran]

(The module keyword inside the interface block is optional as of F2003, but ifort doesn't support that as of 12.1.)

Also, ELEMENTAL procedures may take scalars or arrays (of any rank) - the procedure is called for each element in the array (or just once for the scalar). In this case there is no way for an elemental procedure that is called with an array to find out the size (or shape) of the array that it has been called with - it always sees scalars.

Optional arguments (and elemental procedures) require the procedure to have an explicit interface (it needs to be in a module or you need to have manually provided an interface block for the procedure).

0 Kudos
dboggs
New Contributor I
499 Views
Thanks Ian. I now see that my suggested solution will not work as posted, because both arguments cannot be optional. If that is a real need, you've shown me one way to handle it.

I'm confused by your comment--which also appears in the reference documentation--that "optional arguments require an explicit interface." I find that it is not always required, viz., if there is oneoptional argument and it isat the end. For example, a way of modifying my code is:

! Call to get a single value
CALL SUB (A_1)

! Call to get multiple values
CALL SUB (A_1dummy, A_MULT)

SUBROUTINE SUB (A_1, A_MULT)
REAL :: A_1
REAL, OPTINAL :: A_MULT
IF (PRESENT (A_MULT)) THEN
...(Ignore A_1 and just return A_MULT)...
ELSE
...(Return A_1 only)...
ENDIF


Not as elegant perhaps, but quite adequate, and does not require a module or an interface.

I need to study the module approach more to understand it. I had thought that interfaces were not needed for procedures in a module; they were provided by default. Your solution involves both a module AND an interface, and I realize this is the "generic interface" approach. Maybe there is another approach involving a procedure with two optional arguments (perhaps referenced by keywords) provided via a module?

0 Kudos
TimP
Honored Contributor III
499 Views
In addition to the INTERFACE and MODULE options for optional arguments, there is the internal subroutine option (following CONTAINS). There is no standard option to use OPTIONAL outside one of those 3 contexts. If it happens to work, it's either an extension or just luck with a given implementation.
You asked for a suggestion which would switch between scalar and array argument without violating the standard, and the suggestion of generic interface was the answer. It's true that a scalar argument works the same under the hood as an array length 1 with ifort and other typical implementations on the same architecture, if you defeat standards checking, but many of us remember platforms where that wasn't so.
0 Kudos
IanH
Honored Contributor II
499 Views
Expanding on what Tim wrote...

The explicit in "explicit interface" does not (necessarily) mean "I the programmer explicitly provided it". Instead, what it means is that "the compiler has been able to determine explicitly the interface". The opposite concept is "the compiler implicitly determined what the interface to the procedure was, based on how the programmer called the procedure".

So you are right - procedures in a module (and internal procedures as Tim points out) automatically get an explicit interface - after compiling the module (and hence wherever the module is subsequently USE'd - which must come later in the compilation process) the compiler "knows" things such as the number and type of the arguments that the procedure takes and any attributes that each argument might have.

Procedures that don't get an automatic explicit interface can be given one by the programmer using an interface block.

But interface blocks (things that start with an INTERFACE statement) can be used for more than just providing explicit interfaces for procedures that aren't module procedures or internal procedures.

- If the interface statement consists only of keyword INTERFACE, then you are defining explicit interfaces for procedures that don't otherwise have them.

- If the interface statement has a name after the interface keyword, then it defines a generic interface. The one name may refer to a number of procedures, which must be unambiguously distinguishable in some way by their type, kind, rank, number, etc.

- If the interface statement has the ABSTRACT keyword before the INTERFACE keyword, then it defines an abstract interface - which is a bit like a template for what another (real) procedure must look like somewhere else (perhaps a procedure that is being passed as an argument or a type bound procedure). (The procedure referenced in the abstract interface doesn't itself exist, though ifort 12.1 struggles with that concept under some situations...)

Consider your use of optional arguments without an explicit interface. Typical implementation by a processor is that for the arguments that are missing, a placeholder value or flag (often a zero address) is passed instead of the argument - inside the procedure the PRESENT intrinsic just checks to see whether the placeholder is zero or a real address. But this means that at the point of the call the compiler must know (have explicit knowledge - have an explicit interface) how many arguments the procedure has, otherwise it doesn't know to pass the appropriate placeholder. If the compiler hasn't made arrangements at the point of call for the "the argument is missing" placeholder to be there, then the PRESENT intrinsic is just going to be checking some random garbage in memory. There would then be some very dark storm clouds gathering on the horizon of your program...

0 Kudos
dboggs
New Contributor I
499 Views
I thank everyone, especially Ian, for their comments. This subject is new to me, being an old F77 (not to mention FII and FIV) programmer, where certain "tricks" were well established (and found to be quite useful) that may be frowned upon today.

Though I believe in abiding by the rules, I struggle to learn them because (a) documentation is at times not very clear or simple to follow, leading me to experiment a lot by trial and error, (b) the trial and error process is sometimes flawed by compilers that sometimes allow things to work even when they don't conform to the standard.

The discussion in this forum has been very beneficial to my understanding.
0 Kudos
jimdempseyatthecove
Honored Contributor III
499 Views
Ahh, FORTRAN II

The compiler we had could do something like this

SUBROUTINE ADD_ONE(I)
INTEGER I
I = I + 1
END SUBROUTINE ADD_ONE
...

CALL ADD_ONE(100)
J= 100

The value of J is 101 (IOW the ADD_ONE modified a literal)

Jim Dempsey
0 Kudos
dboggs
New Contributor I
499 Views
Yes I have heard of this behavior but never experienced it personally. Quite amazing. I guess the rule of the day was to not pass literals as arguments, so I just obeyed without question.
0 Kudos
Steven_L_Intel1
Employee
499 Views
This is discussed in Don't Touch Me There - What error 157 (Access Violation) is trying to tell you Early versions of DVF and Intel Fortran on Windows had this misbehavior.
0 Kudos
Reply