Conventions

Since the C++ and Fortran binding code are generated by SWIG-Fortran, most conventions are based on its defaults and built-in wrappers as described in the SWIG+Fortran manual page. This section attempts to describe all conventions used by Flibcpp that may be ambiguous to either a C++ or Fortran user. Since Flibcpp’s target audience is Fortran users, the library tries to follows existing Fortran conventions, and this document attempts to remain true to the official Fortran nomenclature as described in the ISO standards.

Basic types

The C++ standard library features many numeric classes and functions that take a template parameter corresponding to the type of a single element used by the class or algorithm. All such classes or functions that Flibcpp includes are templated on:

  • 32-bit integers (integer(4) or integer(C_INT32_T)), with a suffix of Int4 where applicable;
  • 64-bit integers (integer(8) or integer(C_INT64_T)), with a suffix of Int8 where applicable; and
  • 64-bit reals (real(8) or real(C_DOUBLE)), with a suffix of Real8 where applicable.

In general, most templated C++ functions use Fortran generic procedures to allow a single procedure name to operate on multiple types, and only the derived types (such as VectorInt4) require the suffix.

Error handling

Some modules support error handling for checking input values. In all cases, the error status and a message can be accessed and cleared through the main flc module:

use flc, only : ierr, get_serr

! <snip>
if (ierr /= 0) then
  write(1,*) "Error", ierr, ":", get_serr()
  ! Reset the error flag to indicate that the error has been successfully
  ! handled.
  ierr = 0
fi

Since errors do not immediately exit, it is up to the application code to check for and clear them. Failing to clear the error code may cause the application to exit on a subsequent Flibcpp procedure call.

Indexing

C and C++ use the convention that 0 corresponds to the first element in an array: specifically, it indicates an offset of zero from the array’s beginning. In Fortran, the convention is for the first element to have an index of 1, so Flibcpp has the same convention.

This convention makes native array integration straightforward. For example, when the binary_search algorithm is used with any value val in the sorted array arr, the following logical expression will be true:

arr(binary_search(arr, val)) == val

An additional convention Flibcpp uses is for an invalid index to have a value of zero. Thus, the binary_search algorithm returns zero if asked to find an element that’s not present.

Derived type behavior

The derived types defined by Flibcpp are all “proxy” objects that operate on pointers to C++-owned objects. Some derived type values (i.e. class instances) will “own” the associated data, while some will merely reference data owned by other C++ objects.

Note

Memory management in SWIG-Fortran-generated code is unintuitive and could change. If you have feedback, please contact the author.

Construction

Although Fortran separates the processes of allocation and initialization, C++ combines them into the single act of construction. Thus the proxy objects in Flibcpp can either be in an unassigned, deallocated state or in an allocated and internally consistent state.

Each derived type, such as the String type, is constructed using a module procedure interface with the same name as the type. This procedure, defined in the same module as the derived type, is a function that returns a “constructed” type:

use flc_string, only : String
type(String) :: s

s = String()

Most classes have more than one constructor (i.e. procedure interface) that gives the object a more substantial initial state. For example, numeric vectors can be constructed directly from a Fortran array:

use flc_vector, only : Vector => VectorInt4
type(Vector) :: v, v2
v = Vector([1, 2, 3, 5, 8, 13])

Assignment

SWIG-Fortran, and consequently Flibcpp, defines assignment operators for its types that control memory ownership. Assignment for these types can be grouped into two categories: assignment directly from a Flibcpp function return value, and assignment from an existing Flibcpp derived type value.

Flibcpp (or any other SWIG-Fortran) wrapper code sets the correct ownership flag on a return value: non-owning for raw pointers and references; owning for return-by-value. When the left-hand side of an assignment is uninitialized, it captures the returned value and obtains the correct ownership flag. If the left-hand side is initialized, it is automatically destroyed first.

Assignment from within Fortran is like pointer assignment. The left-hand side becomes a non-owning reference to the right-hand side.

Destruction

Unlike native allocatable Fortran types, Flibcpp derived types are not automatically deallocated when ending a procedure. Therefore to avoid leaking memory, these derived type values must be explicitly cleaned up and

released. This is done by the type-bound subroutine named release:

type(Vector), intent(inout) :: v
call v%release()

If the value v above owns the associated memory (i.e. if it was constructed in user code), then Flibcpp cleans up and deallocates the C++ instance, and sets v to an uninitialized state. If v merely points to existing C++ data, i.e. if it was assigned to the return result of a C++ accessor, then Flibcpp will simply set v to an uninitialized state.