*The code for this tutorial is on GitHub:* https://github.com/sol-prog/mix_fortran_cpp.

Different programming languages have different strengths and while you can express any imaginable algorithm in C++11 sometimes you need to interface your code with legacy codes written in Fortran, or you want to use modern Fortran for his rich matrix operations. This post will present some simple examples of mixed Fortran 2008 and C++11 code, as a side note it is entirely possible to write these examples in pure Fortran or C++, coding every operations from scratch in C++ or using matrix libraries like Eigen.

Mixing Fortran with C (or C++) was a painful experience in the past and the exact mechanism was compiler dependent. Starting with Fortran 2003 you can use a standardized mechanism for interoperability with C, calling Fortran from C and vice versa can be done using the *iso_c_binding* Fortran module.

Let’s start by writting a C++11 code that creates two N by N arrays filled with random numbers. In this post we use the column-major order storage (Fortran order) and not the typical row-major storage used in C or C++ for multidimensional arrays.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | ```
#include <iostream>
#include <vector>
#include <random>
#include <ctime>
using namespace std;
//Fill a vector with random numbers in the range [lower, upper]
void rnd_fill(vector<double> &V, double lower, double upper) {
//use system clock to create a random seed
size_t seed (clock());
//use the default random engine and an uniform distribution
default_random_engine eng(seed);
uniform_real_distribution<double> distr(lower, upper);
for( auto &elem : V){
elem = distr(eng);
}
}
int main() {
size_t N = 3;
vector<double> A(N * N), B(N * N);
//Fill A and B with random numbers in the range [0,1]:
rnd_fill(A, 0.0, 1.0);
rnd_fill(B, 0.0, 1.0);
return 0;
}
``` |

The *iso_c_binding* Fortran module defines numerical types compatible with C and C++ numerical types, for e.g. a C *int* is defined as *c_int* and a C *double* as *c_double*. We are going to implement a Fortran subroutine, callable from C, that multiplies two matrices filled with doubles. For matrix multiplication we are going to use a Fortran intrinsic function named *matmul*:

1 2 3 4 5 6 7 | ```
subroutine matrix_multiply(A, rows_A, cols_A, B, rows_B, cols_B, C, rows_C, cols_C) bind(c)
use iso_c_binding
integer(c_int) :: rows_A, cols_A, rows_B, cols_B, rows_C, cols_C
real(c_double) :: A(rows_A, cols_A), B(rows_B, cols_B), C(rows_C, cols_C)
C = matmul(A, B)
end subroutine matrix_multiply
``` |

Please note the use of *bind(c)* on line 1 and *iso_c_binding* module on line 2, the actual matrix multiplication is done on line 6.

In order to be able to use the above Fortran subroutine in C we need to translate Fortran’s subroutine definition to a C function:

1 | ```
subroutine matrix_multiply(A, rows_A, cols_A, B, rows_B, cols_B, C, rows_C, cols_C) bind(c)
``` |

became:

1 | ```
void matrix_multiply(double *A, int *rows_A, int *cols_A, double *B, int *rows_B, int *cols_B, double *C, int *rows_C, int *cols_C);
``` |

Please note that every variable is declared as a pointer, this is because Fortran uses call by reference by default.

What about a C++ definition for *matrix_multiply* ? We can use the C definition wrapped in an *extern “C”* declaration:

1 2 3 | ```
extern "C" {
void matrix_multiply(double *A, int *rows_A, int *cols_A, double *B, int *rows_B, int *cols_B, double *C, int *rows_C, int *cols_C);
}
``` |

Now, we can modify our original C++ code in order to use *matrix_multiply* for multiplying A and B:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | ```
#include <iostream>
#include <vector>
#include <random>
#include <ctime>
using namespace std;
//Fortran subroutine definition "translated" to C++
extern "C" {
void matrix_multiply(double *A, int *rows_A, int *cols_A, double *B, int *rows_B, int *cols_B, double *C, int *rows_C, int *cols_C);
}
//Fill a vector with random numbers in the range [lower, upper]
void rnd_fill(vector<double> &V, double lower, double upper) {
//use system clock to create a random seed
size_t seed (clock());
//use the default random engine and an uniform distribution
default_random_engine eng(seed);
uniform_real_distribution<double> distr(lower, upper);
for( auto &elem : V){
elem = distr(eng);
}
}
//Print matrix V(rows, cols) storage in column-major format
void print_matrix(vector<double> const &V, int rows, int cols) {
for(int i = 0; i < rows; ++i){
for(int j = 0; j < cols; ++j){
std::cout << V[j * rows + i] << " ";
}
std::cout << std::endl;
}
std::cout << std::endl;
}
int main() {
size_t N = 3;
vector<double> A(N * N), B(N * N), C(N * N);
//Fill A and B with random numbers in the range [0,1]:
rnd_fill(A, 0.0, 1.0);
rnd_fill(B, 0.0, 1.0);
//Multiply matrices A and B, save the result in C
int sz = N;
matrix_multiply(&A[0], &sz, &sz, &B[0], &sz, &sz, &C[0], &sz, &sz);
//print A, B, C on the standard output
print_matrix(A, sz, sz);
print_matrix(B, sz, sz);
print_matrix(C, sz, sz);
return 0;
}
``` |

Please note the syntax used at line 50 for sending a C++ STL vector to our Fortran subroutine by reference.

The above codes can be compiled with gcc-4.7 on Linux and Mac or with Visual Studio and Intel Fortran on Windows.

If you save the Fortran code in *fortran_matrix_multiply.f90* and C++ in *cpp_main_1.cpp* you can compile and link them on Unix:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ```
sols-MacBook-Pro:mix_fortran_cpp sol$ gfortran-4.7 -c fortran_matrix_multiply.f90
sols-MacBook-Pro:mix_fortran_cpp sol$ g++-4.7 -c -std=c++11 cpp_main_1.cpp
sols-MacBook-Pro:mix_fortran_cpp sol$ gfortran-4.7 fortran_matrix_multiply.o cpp_main_1.o -lstdc++ -o mix_example.out
sols-MacBook-Pro:mix_fortran_cpp sol$ ./mix_example.out
0.411975 0.886183 0.041323
0.130066 0.576865 0.507137
0.26862 0.938682 0.385561
0.464278 0.317101 0.3179
0.799268 0.0544081 0.744739
0.0202931 0.0940126 0.447147
0.900407 0.182738 0.809419
0.531748 0.120308 0.697726
0.882796 0.172499 0.956869
sols-MacBook-Pro:mix_fortran_cpp sol$
``` |

On Windows open an Intel Fortran command prompt and write these two lines for compiling and linking the codes:

1 2 | ```
ifort -c fortran_matrix_multiply.f90
cl fortran_matrix_multiply.obj cpp_main_1.cpp.cpp
``` |

If you are interested in learning more about the new C++11 syntax I would recommend reading Professional C++ by M. Gregoire, N. A. Solter, S. J. Kleper 2nd edition:

or, if you are a C++ beginner you could read C++ Primer (5th Edition) by S. B. Lippman, J. Lajoie, B. E. Moo.

If you need to brush your Fortran knowledge a good book is Modern Fortran Explained by M. Metcalf, J. Reid and M. Cohen:

blog comments powered by Disqus