fortranfortran2003

Writing a function that accepts any two numbers (any real or any integer)


I have a function that accepts two numbers and I don't care if they are integers or real or 32bits or 64bits. For the example below, I just write it as a simple multiplication. In Fortran 90 you could do this with an interface block, but you'd have to write 16 (!) functions if you wanted to cover all the possible interactions of multiplying two numbers, each of which could be int32, int64, real32, or real64.

With Fortran 2003 you have some other options like class(*) for polymorphism and I found one way to do this by simply converting all the inputs to reals, before multiplying:

! compiled on linux with gfortran 4.8.5

program main

   integer,   target :: i = 2
   real(4),   target :: x = 2.0
   real(8),   target :: y = 2.0
   character, target :: c = 'a'

   print *, multiply(i,x)
   print *, multiply(x,i)
   print *, multiply(i,i)
   print *, multiply(y,y) 
   print *, multiply(c,c)

contains

function multiply(p,q)

   real :: multiply
   class(*) :: p, q

   real :: r, s

   r = 0.0 ; s = 0.0

   select type(p)

      type is (integer(4)) ; r = p
      type is (integer(8)) ; r = p
      type is (real(4)) ;    r = p
      type is (real(8)) ;    r = p

      class default ; print *, "p is not a real or int"

   end select

   select type(q)

      type is (integer(4)) ; s = q
      type is (integer(8)) ; s = q
      type is (real(4)) ;    s = q
      type is (real(8)) ;    s = q

      class default ; print *, "q is not a real or int"

   end select

   multiply = r * s

end function multiply

end program main

This seems like an improvement. At least the amount of code here is linear in the number of types rather than quadratic, but I wonder if there is still a better way to do this? As you can see I still have to write the select type code twice, changing 'r' to 's' and 'p' to 'q'.

I tried to convert the select type blocks into a function but couldn't get that to work. But I am interested in any and all alternatives that can further improve on this. It seems like this would be a common problem but I so far haven't found any general approach that is better than this.

Edit to add: Apparently there are plans to improve Fortran w.r.t. this issue in the future as noted in the comment by @SteveLionel. @roygvib further provides a link to a specific proposal which also does a nice job of explaining the issue: https://j3-fortran.org/doc/year/13/13-236.txt


Solution

  • Not a solution for generics, but for "converting the select type blocks into a function", the following code seems to work (which might be useful if some nontrivial conversion is included (?)).

    program main
        implicit none
        integer      :: i = 2
        real*4       :: x = 2.0
        real*8       :: y = 2.0
        character(3) :: c = 'abc'
    
        print *, multiply( i, x )
        print *, multiply( x, i )
        print *, multiply( i, i )
        print *, multiply( y, y )
        print *, multiply( c, c )
    
    contains
    
    function toreal( x ) result( y )
        class(*) :: x
        real :: y
    
        select type( x )
            type is (integer)      ; y = x
            type is (real(4))      ; y = x
            type is (real(8))      ; y = x
            type is (character(*)) ; y = len(x)
            class default          ; stop "no match for x"
        endselect
    end
    
    function multiply( p, q ) result( ans )
        class(*) :: p, q
        real :: ans
        ans = toreal( p ) * toreal( q )
    end
    
    end program
    
    ! gfortran-8 test.f90 && ./a.out
       4.00000000    
       4.00000000    
       4.00000000    
       4.00000000    
       9.00000000  
    

    Another approach may be just converting the actual arguments to reals (although it may not be useful for more practical purposes...)

    program main
        implicit none
        integer   :: i = 2
        real*4    :: x = 2.0
        real*8    :: y = 2.0
        character :: c = 'a'
    
        print *, multiply( real(i), real(x) )
        print *, multiply( real(x), real(i) )
        print *, multiply( real(i), real(i) )
        print *, multiply( real(y), real(y) )
        ! print *, multiply( real(c), real(c) )  ! error
    
    contains
    
    function multiply( p, q ) result( ans )
        real :: p, q
        real :: ans
        ans = p * q
    end
    
    end program