Calling C++ from Fortran

extern "C" is used to declare a function or struct follows C linkage conventions. Essentially it ensures the names (symbols) are unmangled just like bind(C) in Fortran.

Concerning arguments, there are a few (if not more) caveats:

  • C and C++ have different definitions of bool, see Issues interfacing between C++ and Fortran - #15 by ivanpribec. In practice, if you have compatible C and C++ compilers, they boil to the same integer type. There’s an example of interfacing Fortran/C/C++ bools in @scivision’s GitHub repository: https://github.com/scivision/fortran-c-cpp-interface/blob/main/src/cxx/bool_main.cxx
  • You cannot call C++ overloaded functions, these are functions with the same name but different types or number of arguments (in Fortran this is called a generic interface). You will have to write wrappers with distinct names instead, e.g.
    // C++ code:
    void f(int);
    void f(double);
    extern "C" void f_i(int i) { f(i); }
    extern "C" void f_d(double d) { f(d); }
    
    You can then re-establish a generic interface on the Fortran side.

I’d also recommended going through the following resources:

A few more practical tips for creating wrappers:

  • Think in terms of Fortran interfaces. C++ libraries are often designed around objects that encapsulate state but are rarely interoperable with C directly. When writing a Fortran/C interface, prefer writing subroutines/functions which perform an “action” associated with those objects which however remain hidden in the C/C++ portion of the wrapper. The random number generator example fits under this pattern; instead of writing wrappers to expose the random number engine and distributions, those are kept hidden on the C++ side and only the “action” of filling an array with normally distributed values is exposed. (There are usage cases where this might not be possible, e.g. if the C++ object has non-trivial initialization or you want to keep just a single reference to an object on the Fortran side. This becomes much more complex as you essentially take control over the object’s lifetime.)
  • Use [] to indicate arrays in prototypes. A C function which accepts an array can be declared in two ways
    void f(int arr[]); // or
    void f(int *arr);
    
    The second form however is also used for pointers to scalars. Technically it makes no difference, but it helps as a visual reminder that arr is supposed to be an array, and not a reference to a scalar. This is also known as “array decay”, i.e. the array arr[] decays into a pointer to it’s first element.
  • Pass constant scalars by value. It’s easier to add the value attribute in Fortran than it is to de-reference a pointer at each point of usage in C.
  • Use const (C) and intent(in) (Fortran) to match intentions. This applies particularly to variables passed by reference. Be careful not to cast const away; read const does not mean constant” and “Const and Optimization in C”.
  • Use containers such as std::span as type- and bound-safe views into (contiguous) Fortran arrays. Such containers “elevate” a decayed pointer into a STL-compatible container. Other libraries may provide similar domain-specific containers; two examples are the Eigen::Map<> and arma::Mat<> matrix classes, which can be used as wrappers for dense matrices.
8 Likes