Passing optional Fortran arguments to C

I am in the process of writing a Fortran API to a C header for a library and I have run into some issues when it comes to implementing optional arguments. Specifically, the Fortran API works by calling on a C API, which allows for optional arguments by passing NULL as the value[1]. I am curious as to what is a standard compliant way of passing Fortran optional arguments to C. These arguments can be variables, strings, arrays, arrays of strings, etc. but not Fortran pointers. Is it enough to mark them as optional in the Fortran method and the C interface block? Can we use the Fortran “presentness”, or lack thereof to indicate NULL in C?

I asked this question on Stack Overflow but I have gotten mixed answers, especially whether the code is standard conformant. (Probably I could have done a better job structuring the question.)

Specific Questions

  1. Do I need to add optional in both the Fortran procedure and the C interface binding block?
  2. Can we use the Fortran present in a procedure, or lack thereof to indicate NULL in C?
  3. Is passing array_size=size(array, kind=c_size_t) considered standard conforming when array itself might not be present? (Assuming that there is some safety check mechanism for array being NULL on the C side).
    3.1. We can’t mark array_size as optional because it uses the value attribute (and we can’t change the C code). An alternative would be to set the array_size=local_size to some local variable which is set to 0 if the array is not present, but in that case what would be the input value of array when not present?
Minimal Working Example
program main
    use, intrinsic :: iso_c_binding
    implicit none
    call null_str_f90("abc"); call null_str_f90()
    call null_array_opt_f90([1, 2, 3, 4, 5]); call null_array_opt_f90()

    contains

    function istring_(o) result(v)
        character(len=*), intent(in) :: o
        character(len=:, kind=c_char), allocatable :: v
        v = trim(o)//c_null_char
    end function istring_

    subroutine null_str_f90(str)
        interface
        subroutine C_API(str_c) bind(C, name="null_str")
          use, intrinsic :: iso_c_binding
          character(len=1, kind=c_char), dimension(*), optional, intent(in) :: str_c  ! is the optional keyword here necessary?
        end subroutine C_API
        end interface
        character(len=*), intent(in), optional :: str
        ! Local variables
        character(len=:, kind=c_char), allocatable :: name_c
        if (present(str)) name_c = istring_(str)
        call C_API(str_c=name_c)  ! is this standard compliant?
    end subroutine null_str_f90

    subroutine null_array_opt_f90(array)
        interface
        subroutine C_API(array, array_size) bind(C, name="null_array")
          use, intrinsic :: iso_c_binding
          integer(c_int), dimension(*), optional :: array
          integer(c_size_t), value, intent(in) :: array_size
        end subroutine C_API
        end interface
        integer(c_int), dimension(:), optional, intent(in) :: array
        ! Is this call even legal? array_size is passed by VALUE but array is not PRESENT
        call C_API(array=array, array_size=size(array, kind=c_size_t))
    end subroutine null_array_opt_f90

end program main
#include <stdio.h>

void null_str(const char *str) {
  if (str) {
    printf("str is present: str is %s\n", str);
  } else {
    printf("str is not present\n");
  }
}

void null_array(int *array, size_t size) {
  if (array) {
    for (int i = 0; i < size; i++) printf("%d ", array[i]);
    printf("\n");
  } else {
    printf("array is not present\n");
  }
}

For me the MWE posted above produces the expected results, but I am not convinced my approach is correct or portable across compilers.


  1. Whether or not that is a good idea is a completely separate matter, but long story short I don’t have any control over the C code. ↩︎

1 Like

Everything looks alright with the example shown except for the part relevant to the quoted comment above.

It is nonconforming in Fortran to reference an absent argument as the first argument (ARRAY) in the SIZE intrinsic.

So you will have to modify the MWE, specifically the null_array_opt procedure and in it, check whether array is present and act accordingly:

   if ( present( array ) ) then
      call C_API( array=array, array_size=size(array, kind=c_size_t) )
   else
      call C_API( array_size=0_c_size_t )
   end if
2 Likes

I suspected as such (about the size(array, kind=c_size_t)). Is doing the following equally valid to your proposed solution?

integer(c_size_t) :: lsize
lsize = 0
if (present(array)) lsize = size(array, kind=c_size_t)
call C_API(array=array, array_size=lsize)

I am trying to keep the C_API call as simple as possible, especially since the procedures can have multiple arguments which are all optional, so the if-else conditionals would become quite complicated. The entire Fortran API is autogenerated based on a bunch of rules and a Python script

Yes, that’s fine.

In the Fortran code, it is somewhat a matter of style as to whether to have code that is equivalent to what a processor might do “under the hood” as with your proposed solution, or with the if-conditionals that is the pedantic way of showing how to work with the optional parameters.

If the former style works better with autogenerated code, then so be it.

1 Like

Thanks a lot for the help!

I am now wondering if the behaviour from gfortran, compiling and running successfully
C_API( array=array, array_size=size(array, kind=c_size_t) ) when array is .not. present, is intentional and if it’s not why it’s not breaking at runtime.

I suspect there is a good reason either way.

There is no numbered constraint against the nonconformant use of an unallocated allocatable variable or a pointer that is not associated (which is what an absent argument can be considered to be) as the ARRAY argument in the SIZE intrinsic. The onus lies on the program author to conform. The processor cannot be relied upon here.