Search code examples
argumentsfortransubroutinepolymorphismfortran2003

FORTRAN: pass unlimited polymorphic subroutine as arguments and other issues


I am programming with FORTRAN oop features. Now I have a subroutine which takes another subroutine as its argument. But I want the subroutine takes unlimited polymorphic subroutine as the argument as well as normal subroutine. For example I have:

    subroutine PassFunc(MyFunc, MyInput)
        class(*), intent(inout) :: MyInput
        interface
            subroutine MyFunc(A, B)
                class(*), intent(in) :: A
                class(*), intent(out) :: B
            endsubroutine MyFunc
        endinterface
        class(*), allocatable :: FuncRes

        select type(MyInput)
        type is(real(8))
            allocate(real(8)::FuncRes)
            select type(FuncRes)
            type is(real(8))
                call MyFunc(MyInput, FuncRes)
                MyInput = MyInput + FuncRes**2
            endselect
        type is(complex(8))
        endselect
    endsubroutine PassFunc

    !Input Functions
    subroutine Func1(A, B)
        class(*), intent(in) :: A
        class(*), intent(out) :: B

        select type(A)
        type is(real(8))
            select type(B)
            type is(real(8))
                B = A + 1
            endselect
        type is(complex(8))
            select type(B)
            type is(complex(8))
                B = A - 1
            endselect
        endselect
    endsubroutine Func1

    subroutine Func2(A, B)
        real(8), intent(in) :: A
        real(8), intent(out) :: B

        B =  A + 1
    endsubroutine Func2

Questions:

  1. I am only allowed to pass an unlimited polymorphic subroutine into "PassFunc". I am not be able to pass a normal function (a function without class(*)). Is there any way to make "PassFunc" take other types of functions? (Example: Func1 works but Func2 doesn't. I got access violation with IVF, though it didn't complain when compiling. Is it possible to make it work? If it is possible, I can make use of other subroutine without modifying.)

  2. In the case, the type of "FuncRes" variable depends on "MyInput". Now the only way I know is to use a nested select type. But in fact, there is no need to do this since "FuncRes" and "MyInput will always be the same type. Is there a way to reduce the nested select type? (It would be a disaster if I have many intermediate variables.)


Solution

  • There are no functions in your example code.

    A procedure with a polymorphic argument requires an explicit interface for the procedure when the procedure is referenced or when such a procedure is a target is a pointer assignment statement. A pointer that references a procedure that requires an explicit interface must also have an explicit interface. This all practically means that you must always have an explicit interface in place for a procedure that requires an explicit interface if you ever want to use it.

    If explicit interfaces are involved, then the characteristics of the procedure being associated with a dummy argument must match. The declared type of an argument is a characteristic of a procedure, therefore the arguments must match in declared type. Match here means "be the same", being compatible in some way is not sufficient.

    When explicit interfaces are available, then compilers should be able to check that the interfaces match. While they are not required to check - a failure to detect a mismatch would probably be regarded as a compiler bug.

    So if polymorphic arguments are involved there is no way of taking other types of procedures.

    But there is nothing generally stopping you writing a wrapper for your procedure that matches the interface that PassFunc wants (so you pass it to PassFunc instead) and that can call Func2 for you.

    SUBROUTINE Func2_wrapper(A,B)
      CLASS(*), INTENT(IN) :: A
      CLASS(*), INTENT(OUT) :: B
      SELECT TYPE (A)
      TYPE IS (REAL)
        SELECT TYPE (B)
        TYPE IS (REAL)
          CALL Func2(A,B)
        END SELECT
      END SELECT
    END SUBROUTINE Func2_wrapper
    

    For your second question, there is no general way of avoiding the nesting with your chosen method of implementation - if you want Func1 to handle any types thrown at its arguments (the far worse situation is any combination of types).

    However, alternative designs may allow you to get away with a single level. It depends somewhat on what you are trying to do... but a more appropriate approach may be to make the operations Func1, Func2 etc deferred bindings of an abstract parent type, with extensions that actually store the data (not using unlimited polymorphic variables) and that then provide the implementations of the operations as overrides of those deferred bindings. This is closer to the first approach in your previous question.