Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling Fortran subroutines with optional arguments from C++

How would I reference a Fortran function in a C++ header that uses optional arguments? Would I have a prototype in the header for each possible combination of calls? Or is this even possible?

For instance, Fortran:

subroutine foo(a, b, c) bind(c)
   real, intent(in), optional :: a, b, c
   ...
end subroutine foo
like image 325
DavidH Avatar asked Oct 17 '16 14:10

DavidH


2 Answers

As Vladimir F answers, under Fortran 2018 (and Fortran 2008+TS29113) it is possible to use the optional attribute for dummy arguments in a C interoperable Fortran procedure.

Under Fortran 2008 that is not possible.  Several compilers still currently do not support this feature.  With these compilers one is still (albeit with more work) able to support "optional" arguments.

The procedure foo of the question is not C-interoperable under F2008 (even with bind(C)).  However, it is possible under F2008 to mimic this idea: have a C-interoperable procedure with type(c_ptr) arguments which wraps the desired Fortran procedure.  This interoperable wrapper can check for null pointers (using C_ASSOCIATED) to determine whether onward passed arguments are present or not - and pass the dereferenced arguments if so.

For example, the Fortran side with a C-interoperable wrapper may look like

module mod

  use, intrinsic :: iso_c_binding

contains

  subroutine foo_centry(a) bind(c,name='foo')
    type(c_ptr), value :: a
    real(c_float), pointer :: a_pass

    nullify(a_pass)
    if (c_associated(a)) call c_f_pointer(a, a_pass)
    call foo(a_pass)
  end subroutine foo_centry

  subroutine foo(a)
    real(c_float), optional :: a
  end subroutine foo

end module mod

Under Fortran 2018 we have this symmetry in the interoperable interface: if the procedure is defined by means other than Fortran but the interoperable interface has an optional argument, then under F2018 we have the result that referencing this procedure with the argument not present means that the null pointer is passed to the procedure.

Under F2008 we need to handle that side too: we again do that with a F2008 non-interoperable procedure which wraps an interoperable procedure with type(c_ptr) arguments: if the argument is present, pass its address; if not, pass C_NULL_PTR.

Such F2008 code may look like

module mod
  use, intrinsic :: iso_c_binding

  interface
     subroutine foo_fentry(a) bind(c,name='foo')
       import c_ptr
       type(c_ptr), value :: a
     end subroutine foo_fentry
  end interface

contains

  subroutine foo(a)
    real(c_float), optional, target :: a

    if (present(a)) then
       call foo_fentry(c_loc(a))
    else
       call foo_fentry(c_null_ptr)
    end if
  end subroutine foo

end module mod

Be aware of limitations here caused by the use of c_loc: for certain cases one may want to use a copy or take other protective measures.

like image 37
francescalus Avatar answered Oct 15 '22 21:10

francescalus


It is not possible, at least portably, unless you make the subroutine bind(C).

Once you make it bind(C), it is just passing of a pointer which can be NULL on the C side.

subroutine foo(a, b, c) bind(C, name="foo")
   use iso_c_binding, only: c_float
   real(c_float), intent(in), optional :: a, b, c
   ...
end subroutine foo

(for greater portability I used real(c_float) from the iso_c_binding module, but that is somewhat tangential to this question)

In C(++)

extern "C"{
  void foo(float *a, float *b, float *c);
}

foo(&local_a, NULL, NULL);

and then you can make a C++ function which calls foo and which employs C++-style optional parameters.

This capability was allowed in Fortran in Technical Specification ISO/IEC TS 29113:2012 on further interoperability of Fortran with C and was later incorporated into Fortran 2018.

like image 57
Vladimir F Героям слава Avatar answered Oct 15 '22 19:10

Vladimir F Героям слава