C binding with nullable argument

I’m writing a Fortran binding to a C function looking like:

void some_function(int * argument);

Argument is passed as pointer because it can be NULL. The user can pass a pointer to a specific integer value to tune the function’s behavior, or pass NULL to let the function select a reasonable default value.

So far I was binding this with:

subroutine some_function(argument) bind(c)
  import :: c_int
  integer(c_int) :: argument ! without the "value" attribute
end

But this form forbids passing c_null_ptr:

call some_function(3)
call some_function(c_null_ptr) ! gfortran: Error: Type mismatch in argument 'argument' at (1); passed TYPE(c_ptr) to INTEGER(4)

This makes me say this binding would be closer to the C++ form:

void some_function(int& argument);

But to bind my C function with nullable argument, I’m forced to write:

subroutine some_function(argument) bind(c)
  import :: c_ptr
  type(c_ptr), value :: argument
end

So the original type of argument is lost, and some extra work is required for the caller:

integer, target :: v
v = 3
call some_function(c_loc(v))
call some_function(c_null_ptr) ! This is OK now

As a nasty consequence, it’s now possible to pass anything to my function:

real, target :: u
u = 8.45
call some_function(c_loc(u)) ! Oops

So:

  1. Is my understanding correct?
  2. If so, is there another way to do this?

I was considering something hacky:

interface hack
    subroutine some_function_int(argument) bind(c, name="some_function")
      import :: c_int
      integer(c_int) :: argument ! without the "value" attribute
    end
    subroutine some_function_ptr(argument) bind(c, name="some_function")
      import :: c_ptr
      type(c_ptr), value :: argument
    end
end interface

It seems to work:

integer, target :: v

call hack(3)
call hack(c_null_ptr)
v = 15
call hack(v)
v = 3
call hack(c_loc(v))

With a dummy implementation:

#include <stdio.h>

int some_function(int * value)
{
    if(value == NULL)
    {
        printf("C > value received = NULL !!\n");
    }
    else
    {
        printf("C > value received = %d\n", *value);
    }
}

I get:

C > value received = 3
C > value received = NULL !!
C > value received = 15
C > value received = 3

But I’m not sure if it works by pure chance, and I’m worried about the legitimacy of this.

I remember I read somewhere (Stackoverflow?) that null will be sent when an optional argument receives no actual argument (indeed, the following code seems to work). But I am not sure whether this behavior is guaranteed by the Standards for C interoperability (or even within pure Fortran codes), so I would like to know if that is the case.

!! fmain.f90
program main
    use iso_c_binding, only: c_int
    implicit none
    interface
        subroutine mysub( n ) bind(c,name="mysub")
            import
            integer(c_int), optional :: n
        end subroutine
    end interface

    call mysub( n=100 )
    call mysub()
end
// cfunc.f
#include <stdio.h>

void mysub(int * value)
{
    if(value == NULL)
    {
        printf("C > value received = NULL !!\n");
    }
    else
    {
        printf("C > value received = %d\n", *value);
    }
}
$ gfortran cfunc.c fmain.f90 && ./a.out
C > value received = 100
C > value received = NULL !!
1 Like

AFAIK, this is guaranteed since F2018. For instance in J3/24-007, section 18.3.7,

If an interoperable procedure defined by means other than Fortran has an optional dummy argument, and the corresponding actual argument in a reference from Fortran is absent, the procedure is invoked with a null pointer for that argument. If an interoperable procedure defined by means of Fortran is invoked by a C function, an optional dummy argument is absent if and only if the corresponding argument in the invocation is a null pointer.

An explanation of this feature can also be found in Modern Fortran explained: incorporating Fortran 2023, section 20.14.


So the solution, would be to use:

subroutine some_function(argument) bind(c)
  import :: c_int
  integer(c_int), optional :: argument ! without "value" attribute
end subroutine
4 Likes

Ohh… thanks very much for your information! The web page I read before (possibly Stackoverflow) did not cite the original reference, so I was not very sure about this feature. Also, my edition of Modern Fortran Explained is somewhat old (green one), so maybe it’s time to get newer ones…

The optional argument appears already in the F2018 version of MFE (the red book).

This is very interesting, it could be helpful when the argument can be considered optional. But it’s not always the case, I can have more, non-optional (and non-pointer) arguments following:

void some_tricky_function(int * nullable_argument, int another_argument);

The C API is not necessarily designed to be Fortran-friendly in the first place. I could make intermediate C functions to adapt the signature of course.

I don’t think it’s a problem. The Fortran interface would simply be:

subroutine some_tricky_function(argument,another_argument) bind(c)
  import :: c_int
  integer(c_int), optional :: argument
  integer(c_int), value    :: another_argument
end subroutine

I was not aware that optional arguments could be at any place in a function’s signature. I thought they had to be placed after compulsory arguments, like C++ or Python.

This becomes an interesting solution indeed.

The only constraint is that if an optional argument is omitted in a call, all the following arguments (optional or not) must be referenced with a keyword:

call some_tricky_function(another_argument=...)`
1 Like

As I mention in another thread, Fortran 2023 adds the syntax .NIL. to indicate omission of an optional argument, so you would no longer be required to use keyword argument names. I’m not aware that any compiler supports this yet.

2 Likes