Pass a scalar actual argument into an array dummy argument

I know it’s non-standard pass a scalar variable into a procedure argument where an array is expected, as discussed in other threads on this site. (But it is acceptable to do the reverse, to pass a single array element into a scalar argument.)

This limitation requires developers to make a special overload procedure (generic interface) to handle the scalar case. Here’s an example of what I had to do in some legacy code when I recently updated compiler versions from gfortran 7 to 10+, because some non-standard code that was previously compiling (either unchecked or by extension) now triggers a compile error for this very reason.

      call inread(iunit, 5, array_variable)    ! read 5 values and store in array elements 1-5
      call inread(iunit, 1, array_variable(6)) ! read 1 value and store in array element 6 by sequence association
      call inread(iunit, 1, scalar_variable)   ! ERROR: rank mismatch with gcc 10+

Here’s a representation of inread’s interface, where val is an array:

      subroutine inread_array(iunit,nvars,defin,val)
         implicit none
         integer iunit
         integer nvars
         character*(*) defin(nvars)
         double precision val(nvars)
      ...

So I had to make a generic interface just to handle the scalar case by just copying the scalar in and out of a 1-element array:

      interface inread
          module procedure inread_array
          module procedure inread_scalar
      end interface inread

contains
      subroutine inread_array(iunit,nvars,defin,val)
          ...
      end subroutine inread_array

      subroutine inread_scalar(iunit,nvars,defin,valout)
         implicit none
         integer iunit
         integer nvars ! must be 1
         character*(*) defin(nvars)
         double precision val(nvars), valout

         call inread_array(iunit,nvars,defin,val)
         valout = val(1) ! reassign the array element to the scalar output variable
      end subroutine inread_scalar

Would it be possible with explicit interfaces for the compiler to simply treat a scalar actual argument as an assumed-size array of size 1 and the proper rank to match the dummy argument?

If the scalar variable were intent(in), you could have gotten away with,

call inread(iunit, 1, [scalar_variable])

If intent(inout) one solution might have been, to promote the scalar to a single-element array,

double precision scalar_variable(1) ! now an array

Sometimes you can get away with this with the array semantics. If that doesn’t work, it’s possible to get a scalar with associate:

program test
double precision scalar_variable(1) ! now an array
scalar_variable = 99
associate(scalar => scalar_variable(1)) 
   scalar = 42
end associate
print *, scalar_variable  ! prints 42.0
end program

I tested this works as expected with gfortran 10. One questionable aspect is you have the same array (element) appearing under two different names. :man_shrugging:

If you don’t like associate, then my last idea (ignoring the preprocessor…) would be search and replace scalar_variable with scalar_variable(1) in the expressions were it must be a scalar. You’d be making no changes in the procedure, only on the caller side.

1 Like

Unfortunately this would be much worse than the annoyance of creating this thin generic interface. This inread subroutine is invoked thousands of times to read dozens of data files, with a mix of scalars and arrays under no particular order. To find every instance it is called with a scalar variable, declare a throwaway 1-element array, replace the argument, and reassign the value, would be daunting compared to the new dozen lines of the generic interface.

I’m not unhappy with this generic interface solution, but it occurred to me that it might be unnecessary if the language could be updated to allow scalars in this context. Matlab generally does allow scalars and arrays to be used interchangeable in function arguments (perhaps because everything is inherently an array?).

At least temporarily, moving forward with -fallow-argument-mismatch could be an option. But in the long run it is just more technical debt.

One more possiblity would be using an assumed rank argument (Compiler Explorer); it works with gfortran 10:

module foo
implicit none
contains
    subroutine inread(iunit,nvars,val)
         implicit none
         integer iunit
         integer nvars
         double precision, contiguous :: val(..)
        print *, "iunit = ", iunit
        print *, "nvars = ", nvars
        select rank(val)
        rank(0)
            if (nvars /= 1) error stop "expected a scalar"
            print *, "val is scalar"
        rank(1)
            if (size(val) < nvars) error stop "size mismatch"
           print *, "val is a 1-d array"
        rank default
            error stop
        end select
     end subroutine
end module

program test
use, intrinsic :: iso_fortran_env, only: iunit => input_unit
use foo, only: inread
implicit none

real(kind(1.0d0)) :: array_variable(6)
real(kind(1.0d0)) :: scalar_variable

call inread(iunit, 5, array_variable)    ! read 5 values and store in array elements 1-5
call inread(iunit, 1, array_variable(6)) ! read 1 value and store in array element 6 by sequence association
call inread(iunit, 1, scalar_variable)   

end program

The disadvantage compare to your generic interface is the rank is resolved at runtime. It also needs error checking, but so would inread_scalar:

      subroutine inread_scalar(iunit,nvars,defin,valout)
         implicit none
         integer iunit
         integer nvars ! must be 1
         character*(*) defin(nvars)
         double precision val(nvars), valout
         if (nvars /= 1) error stop "inread: wrong value for nvars"
         ! ...
      end subroutine inread_scalar

Edit: corrected generic to assumed rank.

1 Like

Would assumed rank work in this case? I’ve never used it so I don’t really know what the limitations on assumed rank are.

1 Like

Assuming no line breaks or other surprises, I reckon this particular case could be handled with a regex:

$ cat inread.f90
      call inread(iunit, 5, array_variable)    ! read 5 values and store in array elements 1-5
      call inread(iunit, 1, array_variable(6)) ! read 1 value and store in array element 6 by sequence association
      call inread(iunit, 1, scalar_variable)   ! ERROR: rank mismatch with gcc 10+
$ grep -inE 'call\s+inread\s*\([^,]+,\s*1\s*,' inread.f90
2:      call inread(iunit, 1, array_variable(6)) ! read 1 value and store in array element 6 by sequence association
3:      call inread(iunit, 1, scalar_variable)   ! ERROR: rank mismatch with gcc 10+

With some search and replace logic it could even try to substitute with a scalar interface (without the nvars),

      subroutine inread_scalar(iunit,defin,valout)
         implicit none
         integer iunit
         character(len=*) defin
         double precision valout
         ! local variables 
         double precision val(1)
         call inread_array(iunit,1,[defin],val)
         valout = val(1)
      end subroutine inread_scalar

An LLM should be quite capable of generating a script to do the replacement; here is just a quick test I did,

# fix_inread.py
import re, sys, difflib

pat = re.compile(r'\bcall\s+inread\s*\(\s*([^,()]+)\s*,\s*1\s*,\s*', re.IGNORECASE)

for fname in sys.argv[1:]:
    with open(fname) as f:
        old = f.read().splitlines()
    new = [pat.sub(r'call inread(\1, ', line) for line in old]
    if new != old:
        diff = difflib.unified_diff(old, new, fromfile=fname, tofile=fname, lineterm='')
        print('\n'.join(diff))

Running this with python3 fix_inread.py *.f90 > inread.patch, produces,

--- inread.f90
+++ inread.f90
@@ -1,3 +1,3 @@
       call inread(iunit, 5, array_variable)    ! read 5 values and store in array elements 1-5
-      call inread(iunit, 1, array_variable(6)) ! read 1 value and store in array element 6 by sequence association
-      call inread(iunit, 1, scalar_variable)   ! ERROR: rank mismatch with gcc 10+
+      call inread(iunit, array_variable(6)) ! read 1 value and store in array element 6 by sequence association
+      call inread(iunit, scalar_variable)   ! ERROR: rank mismatch with gcc 10+

The LLM can iterate on the replacement script and patch as long as it needs to verify every scalar case is replaced correctly.

I created a small test program for assumed rank based on the description of assumed rank in the 2018 version of MFE and it appears to work with ifx 2025.3 and gfortran-15. Here is the code

Program testar

  USE ISO_FORTRAN_ENV, WP=>REAL64, stdin=>INPUT_UNIT

  Implicit NONE
  Real(WP) :: scalar
  Real(WP) :: array(5)

  Call inread(stdin, 1, scalar)
  Call inread(stdin, 5, array)

  Print *,' scalar = ', scalar
  Print *,' array = ', array(1:5)

  STOP

Contains

  Subroutine inread(iunit, nvars, var)

    Integer,  Intent(IN)    :: iunit, nvars
    Real(WP), Intent(INOUT) :: var(..)

    Select Rank(var)

      Rank(0)

        If (nvars/= 1) Then
          Print *, ' nvars /= 1 and var is a scalar'
          var = 0.0_WP
        Else
           Read(iunit,*) var
        End If

       Rank(1)

         Read(iunit,*) var(1:nvars)

       Rank Default

         Print *,' Rank > 1'

    End Select

  End Subroutine inread

End Program testar

The input file was
5.0
1.0 2.0 3.0 4.0 5.0

The biggest problem I see with using assumed rank for this case is that the rank selection is done at run time so if inread is called thousands of time there might be a considerable performance penalty.

I doubt that such a change could be done within any timeframe where it would be useful (i.e. portable across a wide range of compilers).

I think the language was written this way because some compilers would pass scalar arguments by value through registers (essentially copy-in/copy-out through registers) while array arguments were passed by address in a stack frame. However, I’m unsure how the array element argument to scalar association occurred in this case. Does anyone know more about this history of the language?