Flibcpp

Flibcpp uses SWIG-Fortran to generate native Fortran-2003 interfaces to efficient and robust algorithms and data containers implemented in the C++ standard library.

Release:
Date:Jun 21, 2022

Introduction

The Fortran programming language includes many mathematical functions but few of the generic, high-performance software algorithms and data containers needed by essentially all modern scientific software. Most Fortran software contains hand-written code for such algorithms that may be burdensome to maintain and inflexible, as well as unperformant and erroneous under certain conditions.

Flibcpp [1] is a library for use by application developers that provides native Fortran interfaces to existing high-quality algorithms and containers implemented in C++ and available on all modern computer systems.

Flibcpp defines a carefully crafted set of interface files written for the SWIG-Fortran code generator :cite:`johnson_automated_2020`, an extension of SWIG :cite:`beazley_automated_2003`. These Fortran interfaces generate native Fortran proxy code that comprises a set of thin wrappers to selected functionality in the C++ standard library. The resulting code is a set of Fortran modules and C++ wrappers that expose a concise and well-defined interface that may be built into and distributed with the application.

The generated modules include functionality for efficient generic sorting and searching, set operations, random number generation, value mapping, string manipulation, and dynamically resizing vectors.

[1]This documentation is generated from Flibcpp .

Infrastructure

Flibcpp is built using modern CMake, and it has no external dependencies. This makes installation and usage quite simple as long as you have a relatively recent software stack with a Fortran and compatible C++ compiler.

Installation

  1. Download and install CMake if it’s not already on your system. It is highly recommended to use a package manager such as Homebrew for Mac or YUM for Red Hat Linux.
  2. Download the Flibcpp source code from GitHub if you haven’t already.
  3. Create a new build directory (for example purposes, create a subdirectory named build inside your downloaded source directory) and cd to it.
  4. Run CMake: cmake ..
  5. Make and install (by default it will install to /usr/local): make install.

By default, Flibcpp builds shared libraries. Add the CMake argument -DBUILD_SHARED_LIBS:BOOL=OFF to build static libraries.

Downstream usage as a library

The Flibcpp library is most easily used when your downstream app is built with CMake. It should require a single line to initialize:

find_package(Flibcpp REQUIRED CONFIG)

and a single line to link against your app or library:

target_link_libraries(example_backend Flibcpp::flc_random Flibcpp::flc_algorithm)

If your installation prefix for Flibcpp is a system default path (such as /usr/local) or in your $CMAKE_PREFIX_PATH environment variable, it should automatically find the necessary CMake configure file.

An example Fortran application that depends only on Flibcpp is available on Github.

If you’re using a simple standalone Makefile to build your Fortran code, you will have to inform the compiler of the proper include path, library path, and library names. Depending on your system configuration, you might have to also explicitly link your app against the compiler’s C++ standard libraries using -lstdc++.

Downstream usage as a component

Flibcpp’s SWIG interface files can be used with your Fortran-accessible C++ project to seamlessly integrate the Flibcpp Fortran wrapper code with yours. To start, you must have the latest version of the SWIG+Fortran tool installed on your machine: the version of SWIG used by your installation of Flibcpp must match the version used by your downstream library/app. When you build Flibcpp for downstream SWIG usage, you must configure using cmake -DFLIBCPP_USE_SWIG=ON ... This will cause the SWIG interface files to be installed to ${CMAKE_PREFIX_PATH}/include to make them available downstream. Finally, in your downstream SWIG interface code, instead of calling %import <flc.i> you must use %include <import_flc.i>. This is necessary to inject function declarations and other internal macros into your wrapper code.

At that point, all you have to do is (for example) %import <flc_vector> to allow std::vector<double> in your library headers to be wrapped by Flibcpp’s VectorReal8 Fortran proxy derived type.

An example C++/Fortran library that integrates with Flibcpp will be made available on Github.

Developing

If you are interested in extending the capabilities of Flibcpp, you will need the latest version of the SWIG+Fortran tool installed on your machine. When configuring CMake, you will want to configure using cmake -DFLIBCPP_DEV=ON .. to enable tests and documentation. Tests, examples, and documentation can be independently enabled using the FLIBCPP_BUILD_TESTS, FLIBCPP_BUILD_EXAMPLES, and FLIBCPP_BUILD_DOCS options.

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.

Modules

Flibcpp is organized into distinct modules whose structure mirrors the C++ standard library include headers.

The modules themselves are namespaced with a flc_ prefix, so for example the std::sort algorithm, available in the <algorithm> header, can be obtained via:

use flc_algorithm, only : sort

Algorithm

The flc_algorithm module wraps C++ standard algorithm routines. Instead of taking pairs of iterators, the Flibcpp algorithm subroutines accept target-qualified one-dimensional arrays. All algorithms follow the indexing convention that the first element of an array has index 1, and an index of 0 indicates “not found”.

Sorting

Sorting algorithms for numeric types default to increasing order when provided with a single array argument. Numeric sorting routines accept an optional second argument, a comparator function, which should return true if the first argument is strictly less than the right-hand side.

Warning

For every value of a and b, the comparator cmp must satisfy .not. (cmp(a, b) .and. cmp(b, a)). If this strict ordering is not satisfied, some of the algorithms below may crash the program.

All sorting algorithms are also instantiated so that they accept an array of type(C_PTR) and a generic comparator function. This enables arrays of any native Fortran object to be sorted. See the generic sorting example for a demonstration.

sort

Sorting and checking order is a single simple subroutine call:

use flc_algorithm, only : sort
implicit none
integer, dimension(5) :: iarr = [ 2, 5, -2, 3, -10000]

call sort(iarr)
is_sorted

Checking the ordering of array is just as simple:

use flc_algorithm, only : is_sorted
integer, dimension(5) :: iarr = [ 2, 5, -2, 3, -10000]
logical :: sortitude

sortitude = is_sorted(iarr)
argsort

A routine that provides the indices that correspond to a sorted array, like Numpy’s argsort , takes an array to analyze and an empty array of integers to fill:

use flc_algorithm, only : argsort, INDEX_INT
implicit none
integer, dimension(5) :: iarr = [ 2, 5, -2, 3, -10000]
integer(INDEX_INT), dimension(size(iarr)) :: idx

call argsort(iarr, idx)
write(*,*) iarr(idx) ! Prints the sorted array

Note that the index array is always a INDEX_INT, which is an alias to C_INT. On some compilers and platforms, this may be the same as native Fortran integer, but it’s not guaranteed.

The data and idx arguments to argsort must be the same size. If the index array is larger than the data, invalid entries will be filled with zero.

Searching

Like the sorting algorithms, searching algorithms are instantiated on numeric types and the C pointer type, and they provide an optional procedure pointer argument that allows the arrays to be ordered with an arbitrary comparator.

equal_range

Finds the range of elements in a sorted array equivalent to the given value. If the exact value isn’t present, the first index will point to the index at which the value could be inserted to maintain a sorted array. If searching for a value that’s in the sorted array more than once, the expression arr(first_idx:last_idx) will return the equal values. If the value isn’t present, arr(first_idx:last_idx) will be an empty array, and the first index will be the point at which the element would be located if it were present.

Example:

use flc_algorithm, only : equal_range, INDEX_INT
implicit none
integer(INDEX_INT) :: first, last
integer, dimension(6) :: iarr = [ -5, 1, 1, 2, 4, 9]

call equal_range(iarr, -6, first, last) ! (first,last) are (1,0)
call equal_range(iarr, -5, first, last) ! (first,last) are (1,1)
call equal_range(iarr,  1, first, last) ! (first,last) are (2,3)
call equal_range(iarr,  3, first, last) ! (first,last) are (5,4)
call equal_range(iarr,  9, first, last) ! (first,last) are (6,6)
minmax_element

Finds the smallest and largest element in an array. Note that the first occurrence of the minimum value is selected, and the last occurrence of the maximum value is selected. Thus, for a sorted array arr which may have duplicate elements, the expression arr(min_idx:max_idx) will always return the entire array.

Example:

use flc_algorithm, only : minmax_element, INDEX_INT
implicit none
integer, dimension(6) :: iarr = [ -5, 1000, -1000, 999, -1000, 1000]
integer(INDEX_INT) :: min_idx, max_idx

call minmax_element(iarr, min_idx, max_idx) ! min_idx == 3, max_idx == 6

Set operations

Sorted arrays can be manipulated as “sets,” supporting unions, intersections, and differences.

includes

Whether one set encloses another set: every item of the second array is present in the first array.

Example:

use flc_algorithm, only : includes
implicit none
integer, dimension(6) :: iarr = [ -5, 1, 2, 4, 9]
integer, dimension(3) :: jarr = [ 1, 2, 5]
logical :: is_superset

is_superset = includes(iarr, iarr)) ! true
is_superset = includes(iarr, iarr(:3))) ! true
is_superset = includes(iarr, iarr(3:))) ! true
is_superset = includes(iarr(3:), iarr)) ! false
is_superset = includes(iarr, jarr) ! false
is_superset = includes(iarr, jarr(1:2))) ! true
Not yet implemented
  • set_difference
  • set_intersection
  • set_symmetric_difference
  • set_union

Modifying

shuffle

The “shuffle” subroutine depends on the Random module so that it can use the default random number generator to randomly reorder an array.

Example:

use flc_algorithm, only : shuffle
use flc_random, only : Engine => MersenneEngine4
implicit none
integer :: i
integer, dimension(8) :: iarr = (/ ((i), i = -4, 3) /)
type(Engine) :: rng
rng = Engine()

call shuffle(rng, iarr)
Not yet implemented
  • unique

Chrono

Time calculations and timers are not yet implemented.

Map

Maps are sorted dictionaries mapping keys to values. Currently they have limited functionality and few instantiations: maps of ints to ints and of strings to strings.

Basic functionality

All map types support the following basic operations.

Construction and destruction

Like other wrapped C++ classes in Flibcpp, maps are constructed using an interface function. The default constructor is an empty map. Maps are destroyed using the release type-bound subroutine.

Modification

The contents of the map can be changed with the following methods:

insert:
Add a new key-value pair to the map. If the key already exists, the value in the map will remain unchanged. An optional logical parameter can be passed that will be set to .true. if insertion was successful and .false. if the key already existed.
set:
Assign the given value to the key, regardless of whether the value already existed.
get:
Return the value for the specified key, creating it with a default value (zero for numeric types, empty for string types) if it does not exist.
clear:
Remove all items from the map.

The size method returns the number of elements, and count will return the number of elements with the given key.

Here’s an example of creating, modifying, and destroying a map:

use flc_map, only : Map => MapIntInt
type(Map) :: m
logical :: inserted = .false.
integer(C_INT) :: value
m = Map()
call m%insert(123, 456)
call m%insert(123, 567, inserted) ! inserted = false, value unchanged
call m%set(123, 567) ! value is changed
value = m%get(911) ! implicitly created value of zero
call m%erase(911)
call m%release()
Iteration

Iterating over a map to determine its contents is not yet supported.

Random

The flc_random module contains advanced pseudo-random number generation from the random C++ header.

The C++11 way of generating random numbers takes some getting used to. Rather than having a single global function that returns a random real number in the range \([0,1)\), C++11 has independent engine objects that generate streams of random bits. Different distribution objects convert those bits into samples of a distribution.

Although C++11 defines a dizzying variety of random number engines, flc_random wraps just two: the 32- and 64-bit Mersenne Twister algorithms. The 32-bit version (MersenneEngine4) is currently the only engine type that can be used with distributions and algorithms.

Flibcpp wraps distribution objects as independent subroutines. Each subroutine accepts the constructor parameters of the distribution, the random engine, and a target Fortran array to be filled with random numbers.

Generating an array with 10 normally-distributed reals with a mean of 8 and a standard deviation of 2 is done as such:

use flc_random, only : Engine => MersenneEngine4, normal_distribution
real(C_DOUBLE), dimension(20) :: arr
type(Engine) :: rng

rng = Engine()
call normal_distribution(8.0d0, 2.0d0, rng, arr)
call rng%release()

Engines

The two Mersenne twister engines in flc_random return different-sized integers per call:

  • MersenneEngine4: each invocation returns a 32-bit integer(4)
  • MersenneEngine8: each invocation returns a 64-bit integer(8)

Engines can be constructed using one of two interface functions: the argument-free MersenneEngine4() uses the default seed, and the engine takes a single argument MersenneEngine4(1234567) with the seed. Alternatively, the seed can be set (or reset) using the seed() type-bound procedure.

Generally, engines are used with distributions (described below). However, if necessary, individual randomly generated values can be obtained by calling the next() type-bound procedure.

Warning

C++ generates unsigned integers with entropy in every bit. This means that the integers obtained from engine%next(), reinterpreted as signed Fortran integers, may be negative.

In most cases, the default distribution-compatible MersenneEngine4 should be used, since the distributions described below require it.

Distributions

Distributions produce numerical values from the random bitstream provided by an RNG engine. For efficiency, each distribution subroutine accepts an array of values that are filled with samples of the distribution.

normal_distribution

Each element of the sampled array is distributed according to a Gaussian function with the given mean and standard deviation.

uniform_int_distribution

Each element is uniformly sampled between the two provided bounds, inclusive on both sides.

uniform_real_distribution

Each element is a sample of a uniform distribution between the two bounds, inclusive on left side only.

discrete_distribution

The discrete distribution is constructed with an array of \(N\) weights: the probability that an index in the range \([1, N]\) will be selected.

real(C_DOUBLE), dimension(4), parameter :: weights &
   = [.125d0, .125d0, .25d0, .5d0]
integer(C_INT), dimension(1024) :: sampled
call discrete_distribution(weights, Engine(), sampled)

In the above example, 1 and 2 will are expected to each occupy an eighth of the sampled array, approximately a quarter of the sampled array’s values will be 3, and about a half will be 4.

Note

The C++ distribution returns values in \([0, N)\), so in accordance with Flibcpp’s indexing convention the result is transformed when provided to Fortran users.

Set

Sets are sorted containers of unique elements. The flc_set module defines sets of integer and of type(String).

Basic functionality

All set types support the following basic operations.

Construction and destruction

Like other wrapped C++ classes in Flibcpp, sets are constructed using an interface function. The default constructor is an empty set. Sets are destroyed using the release type-bound subroutine.

Modification

The two primary operations on a set are insert and erase for adding an element to and removing an element from the set. A clear subroutine removes all elements from the set.

The size method returns the number of elements, and count will return the number of elements of a given value.

Here’s an example of creating, modifying, and destroying a set:

use flc_set, only : Set => SetInt
type(Set) :: s
logical :: inserted
s = Set()
call s%insert(2)
call s%insert(3, inserted) ! Set has 2 elements, inserted => true
call s%insert(3, inserted) ! Duplicate element, ignored; inserted => false
call s%erase(2) ! Remove 2 from the set
call s%erase(1) ! Nonexistent set element, ignored
write(0,*) "Number of 3s in the set:" s%count(3)
call s%clear() ! Remove all elements, size is now zero
call s%insert(1)
call s%release() ! Free memory
Set operations

The Fortran Set classes have been extended to include several useful set algorithms. (In C++, these are implemented using the <algorithm> header and therefore should resemble the functions in the flc_algorithm module.

All set operations take a single argument, another Set object, and do not modify either the original or the argument. All but the includes return newly allocated Set instances and do not modify the original sets.

difference: \(A \setminus B\)
Returns a new set with all elements from the original that are not present in the other set.
intersection: \(A \cap B\)
Return all elements that are in both sets.
symmetric_difference: \((A \setminus B) \cup (B \setminus A)\)
Return all elements that are in one set or the other but not both.
union: \(A \cup B\)
Return all elements that are in either set.
includes: \(A \supseteq B\)
Return whether all elements of the other set are in the original set.
Iteration

Iterating over a set to determine its contents is not yet supported.

Numeric sets

Unlike vectors, the flc_set module includes a single “native integer” numeric instantiations. The value type is integer(C_INT) and is 64 bits on most modern systems. Since the C++ implementation of numerical sets is not very efficient, the assumption is that the set will be used in a non-numerically-intensive capacity where the default integer is the most appropriate option.

Construct from an array

Numeric sets can be created very efficiently from Fortran data by accepting an array argument:

use flc_set, only : Set => SetInt
type(Set) :: s

s = Set([1, 1, 2, 10])
write(0,*) "Size should be 3:", s%size()

The assign bound method acts like a constructor but for an existing set.

String sets

The native “element” type of SetString is a character(len=:). Set operations that accept an input will take any native character string; and returned values will be allocatable character arrays.

An additional insert_ref function allows assignment of String types

String

The string module includes the String derived type and a handful of string conversion functions.

String type

The C++ standard library “string” is a dynamically resizable, mutable character array.

Constructors

Strings are constructed using three interface functions:

  • The function without arguments creates an empty string;
  • An integer argument count and a single character will create a string of size count filled with that character; and
  • A standard Fortran character(kind=C_CHAR, len=*) which will be copied to the string.

Here are three examples of initialization:

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

s = String()
! s%size() == 0
s = String(10, "!")
! s%size() == 10
! s%get(i) == "!"
s = String("I am a string!")
Character element access

The number of characters in the string is returned by the bound function size. The get function returns the character at an index; and front and back are aliases for get(1) and get(v%size()), respectively.

Important

Unlike the C++ version of this class, strings in Flibcpp use 1-offset indexing. See Indexing.

Modification

Like vectors, Strings can be resized dynamically using a variety of methods:

  • resize to specify an exact size;
  • push_back to add a new character to the end of it;
  • append to add another string to the end
  • pop_back to remove the last character;
  • clear to remove all elements.

The string also has a set bound subroutine for assigning a character to a specified index:

type(String) :: s

s = String("=", 10)
call s%set(1, "8")
call s%set(s%size(), "D")
View as an array pointer

The string can be viewed (and indeed modified) as an array of character elements:

type(String) :: s
character, dimension(:), pointer :: charptr

s = String("Hello!")
charptr => s%view()
charptr(6) = "?" ! change greeting to a question
Conversion to native string

The str type-bound function returns an allocated character string:

character(len=:), allocatable :: native
type(String) :: s

s = String("Hello world")
native = s%str()
write(0,"(a)") native

Conversion functions

The flc_string module includes several module procedures for converting native Fortran strings to integers and real numbers. These functions are robust and exception-safe, allowing intelligent error handling from the Fortran side.

  • Integer conversion: stoi, stol, stoll
  • Real conversion: stof, stod
use flc, only : ierr, get_serr, SWIG_OverflowError, SWIG_ValueError
use flc_string
implicit none
integer(4) :: temp
character(len=100) :: tempstr

read(*, '(a)') tempstr
temp = stoi(trim(tempstr))
if (ierr == SWIG_OverflowError) then
  write(0,*) "Your integer is too darn big!"
elseif (ierr == SWIG_ValueError) then
  write(0,*) "That thing you entered? It wasn't an integer."
end if

Integer conversion defaults to base-10, but passing an additional integer argument allows conversion from other bases. The special integer value of 0 allows auto-detection of values in octal (with a leading 0 as in 0777) or hexadecimal (with a leading 0x as in 0xb1f1c2a3).

Vector

Vectors are resizeable arrays of elements. The flc_vector module instantiates vectors of integer(4), integer(8), real(8), complex(8), and type(String).

Common functionality

All vector types support the following basic operations.

Construction and destruction

Vectors are constructed using four interface functions:

  • The function without arguments creates an empty vector;
  • A single integer argument assigns that many elements with default values; and
  • An integer argument followed by an element with the vector’s element type will copy that value to all elements of the vector.
  • A vector object will create a copy of that vector.

Here are three examples of initialization:

use flc_vector, only : Vector => VectorInt4
type(Vector) :: v

v = Vector()
! v%size() == 0
v = Vector(10)
! v%size() == 10
! v%get(i) == 0
v = Vector(10, 123)
! v%size() == 10
! v%get(i) == 123

Vectors are destroyed using the release type-bound subroutine:

call v%release()
Modification

Vectors can be resized dynamically using resize, which acts like the constructors described above. An element can be added to the end of the vector (increasing the size by one) with push_back. The insert method can insert an element at a specific index, and erase removes a specific vector index or range of indices. clear removes all elements. Finally, set sets the value of an element at a given index.

Important

Unlike the C++ version of this class, all vectors in Flibcpp use 1-offset indexing. This means that v%get(1) is the same as the C++ v[0]: it returns the first element (i.e. the element with an offset of zero).

Here’s an example of modifying a vector:

use flc_vector, only : Vector => VectorInt4
type(Vector) :: v
v = Vector()
call v%resize(4, 123) ! give each element the value 123
call v%push_back(-1) ! size increased by 1, last element has value -1
call v%insert(2, -2) ! First 3 elements are [123, 123, -2]
call v%erase(1, 3) ! Remove the first two elements
call v%erase(2) ! Remove the second element
call v%set(1, -123) ! Change the value of the first element
call v%clear() ! Remove all elements, size is now zero
Access

The size of a vector is returned by the bound function size; get returns the value at an index; and front and back are aliases for get(1) and get(v%size()), respectively.

Additionally, front_ref, back_ref, and get_ref return Fortran pointers to the elements of the array.

Warning

Array element pointers are valid only as long as the vector’s size is not changed. Calling erase, push_back, and so forth will invalidate the pointer; accessing it at that point results in undefined behavior.

Numeric vectors

As with the algorithms and other methods, the flc_vector module includes three scalar numeric instantiations, but it also includes an instantiation for complex numbers. Each instantiation has a distinct derived type:

  • VectorInt4: each element is integer(4)
  • VectorInt8: each element is integer(8)
  • VectorReal8: each element is real(8)
  • VectorComplex8: each element is complex(8)
Construct from an array

Numeric vectors can be created very efficiently from Fortran data by accepting an array pointer:

use flc_vector, only : Vector => VectorInt4
integer(4), dimension(4), parameter :: iarr = [ 1, -2, 4, -8 ]
type(Vector) :: v

v = Vector(iarr)
write(0,*) "Size should be 4:", v%size()

The assign bound method acts like a constructor but for an existing vector.

View as an array pointer

Numeric vectors can also return an array pointer to the vector’s contents. These views support native Fortran array operations and access the same underlying memory as the C++ object:

use flc_vector, only : Vector => VectorInt4
integer(4), dimension(:), pointer :: vptr
type(Vector) :: v

! <snip>
vptr => v%view()
if (size(vptr) > 2) then
   vptr(2) = 4
end if

Warning

A vector’s view is valid only as long as the vector’s size is not changed. Calling erase, push_back, and so forth will invalidate the view; accessing it at that point results in undefined behavior.

String vectors

The native “element” type of VectorString is a character(len=:). Vector operations that accept an input will take any native character string; and returned values will be allocatable character arrays.

The front_ref, back_ref, and get_ref functions allow the underlying std::string class to be accessed with the String Fortran derived type wrapper. Note that unlike for intrinsic types, where these functions return a integer, pointer, the vector of strings returns just type(String). However, as with native pointers described above, these references are invalid once the string changes size. They should be cleared with the %release() bound method.

An additional set_ref function allows vector elements to be assigned from String types.

Examples

The following standalone codes demonstrate how Flibcpp can be used in native Fortran code.

Random numbers and sorting

This simple example generates an array of normally-distributed double-precision reals, sorts them, and then shuffles them again.

 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
!-----------------------------------------------------------------------------!
! \file   example/sort.f90
!
! Copyright (c) 2019 Oak Ridge National Laboratory, UT-Battelle, LLC.
!-----------------------------------------------------------------------------!

program sort_example
  use, intrinsic :: ISO_C_BINDING
  use flc
  use flc_algorithm, only : sort, shuffle
  use flc_random, only : Engine => MersenneEngine4, normal_distribution
  use example_utils, only : write_version, read_positive_int, STDOUT
  implicit none
  integer :: arr_size
  real(c_double), dimension(:), allocatable :: x
  real(c_double), parameter :: MEAN = 1.0d0, SIGMA = 0.5d0
  type(Engine) :: rng

  ! Print version information
  call write_version()

  ! Get array size
  arr_size = read_positive_int("array size")
  allocate(x(arr_size))

  ! Fill randomly with normal distribution
  rng = Engine()
  call normal_distribution(MEAN, SIGMA, rng, x)

  ! Sort the array
  call sort(x)
  write(STDOUT, "(a, 4(f8.3,','))") "First few elements:", x(:min(4, size(x)))

  ! Rearrange it randomly
  call shuffle(rng, x)
  write(STDOUT, "(a, 4(f8.3,','))") "After shuffling:", x(:min(4, size(x)))

  call rng%release()
end program

!-----------------------------------------------------------------------------!
! end of example/sort.f90
!-----------------------------------------------------------------------------!

Vectors of strings

Strings and vectors of strings can be easily manipulated and converted to and from native Fortran strings.

 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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
!-----------------------------------------------------------------------------!
! \file   example/vecstr.f90
!
! Copyright (c) 2019 Oak Ridge National Laboratory, UT-Battelle, LLC.
!-----------------------------------------------------------------------------!

program vecstr_example
  use, intrinsic :: ISO_C_BINDING
  use flc
  use flc_string, only : String
  use flc_vector, only : VectorString
  use example_utils, only : read_strings, write_version, STDOUT
  implicit none
  integer :: i
  type(VectorString) :: vec
  type(String) :: back, front, temp
  character(C_CHAR), dimension(:), pointer :: chars

  ! Print version information
  call write_version()

  ! Read a vector of strings
  call read_strings(vec)

  write(STDOUT, "(a, i3, a)") "Read ", vec%size(), " strings:"
  do i = 1, vec%size()
    write(STDOUT, "(i3, ': ', a)") i, vec%get(i)
  end do

  if (vec%empty()) then
    write(STDOUT, *) "No vectors provided"
    call vec%release()
    stop 0
  endif

  ! Get the final string for modification
  back = vec%back_ref()
  chars => back%view()
  temp = String(back%str())
  ! Change all characters to exclamation points
  chars(:) = '!'
  write(STDOUT, *) "The last string is very excited: " // vec%get(vec%size())

  ! Modify a reference to the front value
  front = vec%front_ref()
  call front%push_back("?")

  ! Insert the original 'back' after the first string (make it element #2)
  call vec%insert(2, temp%str())
  ! Inserting the vector invalidates the 'chars' view and back reference.
  chars => NULL()
  back = vec%back_ref()
  write(STDOUT, *) "Inserted the original last string: " // vec%get(2)
  
  ! Modify back to be something else. 
  call back%assign("the end")

  write(STDOUT, *) "Modified 'front' string is " // vec%get(1)
  write(STDOUT, *) "Modified 'back' string is " // vec%get(vec%size())

  ! Remove the first string (invalidating back and front references)
  call vec%erase(1)
  call back%release()
  call front%release()

  write(STDOUT, "(a, i3, a)") "Ended up with ", vec%size(), " strings:"
  do i = 1, vec%size()
    write(STDOUT, "(i3, ': ', a)") i, vec%get(i)
  end do

  ! Free allocated vector memory
  call vec%release()
end program

!-----------------------------------------------------------------------------!
! end of example/sort.f90
!-----------------------------------------------------------------------------!

Generic sorting

Since sorting algorithms often allow \(O(N)\) algorithms to be written in \(O(\log N)\), providing generic sorting routines is immensely useful in applications that operate on large chunks of data. This example demonstrates the generic version of the argsort subroutine by sorting a native Fortran array of native Fortran types using a native Fortran subroutine. The only C interaction needed is to create C pointers to the Fortran array entries and to provide a C-bound comparator that converts those pointers back to native Fortran pointers. [1]

  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
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
!-----------------------------------------------------------------------------!
! \file   example/sort_generic.f90
!
! Copyright (c) 2019 Oak Ridge National Laboratory, UT-Battelle, LLC.
!-----------------------------------------------------------------------------!

! Mock-up of a user-created type and comparison operator
module sort_generic_extras
  implicit none
  public

  ! Declare an example Fortran derived type
  type :: FortranString
    character(len=:), allocatable :: chars
  end type

  ! Declare a 'less than' operator for that type
  interface operator(<)
    module procedure fortranstring_less
  end interface

contains

! Lexicographically compare strings of equal length.
elemental function chars_less(left, right, length) &
    result(fresult)
  character(len=*), intent(in) :: left
  character(len=*), intent(in) :: right
  integer, intent(in) :: length
  logical :: fresult
  integer :: i, lchar, rchar

  ! If any character code is less than the RHS, it is less than.
  do i = 1, length
    lchar = ichar(left(i:i))
    rchar = ichar(right(i:i))
    if (lchar < rchar) then
      fresult = .true.
      return
    elseif (lchar > rchar) then
      fresult = .false.
      return
    endif
  end do

  fresult = .false.
end function

elemental function fortranstring_less(self, other) &
    result(fresult)
  type(FortranString), intent(in) :: self
  type(FortranString), intent(in) :: other
  logical :: fresult

  if (.not. allocated(other%chars)) then
    ! RHS is null and LHS is not
    fresult = .true.
  elseif (.not. allocated(self%chars)) then
    ! LHS is null => "greater than" (if LHS is string) or equal (if both null)
    fresult = .false.
  elseif (len(self%chars) < len(other%chars)) then
    ! Since LHS is shorter, it is "less than" the RHS.
    fresult = .true.
  elseif (len(self%chars) > len(other%chars)) then
    ! If RHS is shorter
    fresult = .false.
  else
    ! Compare strings of equal length
    fresult = chars_less(self%chars, other%chars, len(self%chars))
  endif
end function

! C++-accessible comparison function for two pointers-to-strings
! (null strings always compare "greater than" to move to end of a list)
function compare_strings(lcptr, rcptr) bind(C) &
    result(fresult)
  use, intrinsic :: ISO_C_BINDING
  type(C_PTR), intent(in), value :: lcptr
  type(C_PTR), intent(in), value :: rcptr
  logical(C_BOOL) :: fresult
  type(FortranString), pointer :: lptr
  type(FortranString), pointer :: rptr

  if (.not. c_associated(rcptr)) then
    ! RHS is null and LHS is not
    fresult = .true.
  elseif (.not. c_associated(lcptr)) then
    ! LHS is null => "greater than" (if LHS is string) or equal (if both null)
    fresult = .false.
  else
    ! Both associated: convert from C to Fortran pointers
    call c_f_pointer(cptr=lcptr, fptr=lptr)
    call c_f_pointer(cptr=rcptr, fptr=rptr)

    ! Compare the strings
    fresult = (lptr < rptr)
  endif
end function
end module

program sort_generic_example
  use, intrinsic :: ISO_FORTRAN_ENV
  use, intrinsic :: ISO_C_BINDING
  use flc
  use flc_algorithm, only : argsort, INDEX_INT
  use sort_generic_extras, only : compare_strings, FortranString
  use example_utils, only : write_version, read_positive_int, STDOUT, STDIN
  implicit none
  type(FortranString), dimension(:), allocatable, target :: fs_array
  type(C_PTR), dimension(:), allocatable, target :: ptrs
  integer(INDEX_INT), dimension(:), allocatable, target :: ordering
  character(len=80) :: readstr
  integer :: arr_size, i, io_ierr

  call write_version()

  ! Read strings
  arr_size = read_positive_int("string array size")
  allocate(fs_array(arr_size))
  do i = 1, arr_size
    write(STDOUT, "(a, i3)") "Enter string #", i
    read(STDIN, "(a)", iostat=io_ierr) readstr
    if (io_ierr == IOSTAT_END) then
      ! Leave further strings unallocated
      exit
    endif
    ! Allocate string
    allocate(fs_array(i)%chars, source=trim(readstr))
  enddo

  ! Create C pointers to the Fortran objects
  ptrs = [(c_loc(fs_array(i)), i = 1, arr_size)]

  ! Use 'argsort' to determine the new ordering
  allocate(ordering(arr_size))
  call argsort(ptrs, ordering, compare_strings)
  write(STDOUT, "(a, 20(i3))") "New order:", ordering

  ! Reorder the Fortran data
  fs_array = fs_array(ordering)

  ! Print the results
  write(STDOUT, *) "Sorted:"
  do i = 1, arr_size
    if (.not. allocated(fs_array(i)%chars)) then
      write(STDOUT, "(i3, '-', i3, a)") i, arr_size, " are unallocated"
      exit
    endif
    write(STDOUT, "(i3, ': ', a)") i, fs_array(i)%chars
  enddo

end program

!-----------------------------------------------------------------------------!
! end of example/sort.f90
!-----------------------------------------------------------------------------!

Example utilities module

This pure-Fortran module builds on top of functionality from Flibcpp. It provides procedures to:

  • Format and print the Flibcpp version;
  • Converts a user input to an integer, validating it with useful error messages;
  • Reads a dynamically sized vector of strings from the user.
  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
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
!-----------------------------------------------------------------------------!
! \file   example/example_utils.f90
! \brief  example_utils module
! \note   Copyright (c) 2019 Oak Ridge National Laboratory, UT-Battelle, LLC.
!-----------------------------------------------------------------------------!

module example_utils
  use, intrinsic :: ISO_FORTRAN_ENV
  use, intrinsic :: ISO_C_BINDING
  implicit none
  integer, parameter :: STDOUT = OUTPUT_UNIT, STDIN = INPUT_UNIT
  public
    
contains

subroutine write_version()
  use flc
  implicit none
  ! Print version information
  write(STDOUT, "(a)") "========================================"
  write(STDOUT, "(a, a)") "Flibcpp version: ", get_flibcpp_version()
  write(STDOUT, "(a, 2(i1,'.'), (i1), a)") "(Numeric version: ", &
      flibcpp_version_major, flibcpp_version_minor, flibcpp_version_patch, &
      ")"
  write(STDOUT, "(a)") "========================================"
end subroutine
    
! Loop until the user inputs a positive integer. Catch error conditions. 
function read_positive_int(desc) result(result_int)
  use flc
  use flc_string, only : stoi
  implicit none
  character(len=*), intent(in) :: desc
  character(len=80) :: readstr
  integer :: result_int, io_ierr
  do
    write(STDOUT, *) "Enter " // desc // ": "
    read(STDIN, "(a)", iostat=io_ierr) readstr
    if (io_ierr == IOSTAT_END) then
      ! Error condition: ctrl-D during input
      write(STDOUT, *) "User terminated"
      stop 1
    endif

    result_int = stoi(readstr)
    if (ierr == 0) then
      if (result_int <= 0) then
        ! Error condition: non-positive value
        write(STDOUT, *) "Invalid " // desc // ": ", result_int
        continue
      end if

      write(STDOUT, *) "Read " // desc // "=", result_int
      exit
    endif

    if (ierr == SWIG_OVERFLOWERROR) then
      ! Error condition: integer doesn't fit in native integer
      write(STDOUT,*) "Your integer is too darn big!"
    else if (ierr == SWIG_VALUEERROR) then
      ! Error condition: not an integer at all
      write(STDOUT,*) "That text you entered? It wasn't an integer."
    else
      write(STDOUT,*) "Unknown error", ierr
    end if
    write(STDOUT,*) "(Detailed error message: ", get_serr(), ")"

    ! Clear error flag so the next call to stoi succeeds
    ierr = 0
  end do
end function

! Loop until the user inputs a positive integer. Catch error conditions. 
subroutine read_strings(vec)
  use flc
  use flc_string, only : String
  use flc_vector, only : VectorString
  use ISO_FORTRAN_ENV
  implicit none
  type(VectorString), intent(out) :: vec
  integer, parameter :: STDOUT = OUTPUT_UNIT, STDIN = INPUT_UNIT
  character(len=80) :: readstr
  integer :: io_ierr
  type(String) :: str

  ! Allocate the vector
  vec = VectorString()

  do
    ! Request and read a string
    write(STDOUT, "(a, i3, a)") "Enter string #", vec%size() + 1, &
        " or Ctrl-D/empty string to complete"
    read(STDIN, "(a)", iostat=io_ierr) readstr
    if (io_ierr == IOSTAT_END) then
      ! Break out of loop on ^D (EOF)
      exit
    end if

    ! Add string to the end of the vector
    call vec%push_back(trim(readstr))
    ! Get a String object reference to the back to check if it's empty
    str = vec%back_ref()
    if (str%empty()) then
      ! Remove the empty string
      call vec%pop_back()
      exit
    end if
  end do
end subroutine

end module

!-----------------------------------------------------------------------------!
! end of example/example_utils.f90
!-----------------------------------------------------------------------------!

Footnotes

[1]

Older versions of Gfortran (before GCC-8) fail to compile the generic sort example because of a bug that incorrectly claims that taking the C pointer of a scalar Fortran value is a violation of the standard:

../example/sort_generic.f90:84:38:

     call c_f_pointer(cptr=rcptr, fptr=rptr)
                                      1
Error: TS 29113/TS 18508: Noninteroperable array FPTR at (1) to
C_F_POINTER: Expression is a noninteroperable derived type

See this bug report for more details.

References

Acknowledgments

This research was supported by the Exascale Computing Project (17-SC-20-SC), a joint project of the U.S. Department of Energy’s Office of Science and National Nuclear Security Administration, responsible for delivering a capable exascale ecosystem, including software, applications, and hardware technology, to support the nation’s exascale computing imperative.

This research used resources of the Oak Ridge Leadership Computing Facility, which is a DOE Office of Science User Facility supported under Contract DE-AC05-00OR22725.

Interface

These are the SWIG interface files used to generate the Flibcpp modules.

flc

The primary file defines typemaps.

 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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/*!
 * \file flc.i
 *
 * Copyright (c) 2019-2020 Oak Ridge National Laboratory, UT-Battelle, LLC.
 * Distributed under an MIT open source license: see LICENSE for details.
 */

%module "flc"

#if defined(SWIGIMPORTED) && !defined(FLC_SWIGIMPORTED)
#error "To import the FLC module correctly, use ``%include \"import_flc.i\"``"
#endif

/* -------------------------------------------------------------------------
 * Header definition macros
 * ------------------------------------------------------------------------- */

%define %flc_add_header
%insert("fbegin") %{
! Flibcpp project, https://github.com/swig-fortran/flibcpp
! Copyright (c) 2019-2020 Oak Ridge National Laboratory, UT-Battelle, LLC.
! Distributed under an MIT open source license: see LICENSE for details.
%}
%insert("begin") %{
/*
 * Flibcpp project, https://github.com/swig-fortran/flibcpp
 * Copyright (c) 2019-2020 Oak Ridge National Laboratory, UT-Battelle, LLC.
 * Distributed under an MIT open source license: see LICENSE for details.
 */
%}
%enddef

%flc_add_header

/* -------------------------------------------------------------------------
 * Exception handling
 * ------------------------------------------------------------------------- */

// Rename the error variables' internal C symbols
#define SWIG_FORTRAN_ERROR_INT flc_ierr
#define SWIG_FORTRAN_ERROR_STR flc_get_serr

// Restore names in the wrapper code
%rename(ierr) flc_ierr;
%rename(get_serr) flc_get_serr;

// Unless we're directly building this module, delay exception handling
#ifndef SWIGIMPORTED
%include <exception.i>
#endif

/* -------------------------------------------------------------------------
 * Data types and instantiation
 * ------------------------------------------------------------------------- */

// Note: stdint.i inserts #include <stdint.h>
%include <stdint.i>

%define %flc_template_numeric(SRC, DST)
%template(DST) SRC<int32_t>;
%template(DST) SRC<int64_t>;
%template(DST) SRC<double>;
%enddef

/* -------------------------------------------------------------------------
 * Array view translation
 * ------------------------------------------------------------------------- */

%include <typemaps.i>
%apply (SWIGTYPE *DATA, size_t SIZE) {
       (int32_t  *DATA, size_t DATASIZE),
       (int64_t  *DATA, size_t DATASIZE),
       (double   *DATA, size_t DATASIZE),
       (void    **DATA, size_t DATASIZE)};

%apply (SWIGTYPE const *DATA, size_t SIZE) {
       (int32_t  const *DATA, size_t DATASIZE),
       (int64_t  const *DATA, size_t DATASIZE),
       (double   const *DATA, size_t DATASIZE),
       (void*    const *DATA, size_t DATASIZE)};

/* -------------------------------------------------------------------------
 * Version information
 * ------------------------------------------------------------------------- */

%apply char* { const char flibcpp_version[] };
%fortranbindc flibcpp_version_major;
%fortranbindc flibcpp_version_minor;
%fortranbindc flibcpp_version_patch;

// These symbols are defined in the CMake-generated `flibcpp_version.cpp`
%inline %{
extern "C" {
extern const char flibcpp_version[];
extern const int flibcpp_version_major;
extern const int flibcpp_version_minor;
extern const int flibcpp_version_patch;
}
%}

flc_algorithm

  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
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/*!
 * \file flc_algorithm.i
 *
 * Copyright (c) 2019 Oak Ridge National Laboratory, UT-Battelle, LLC.
 * Distributed under an MIT open source license: see LICENSE for details.
 */

%module "flc_algorithm"
%include "import_flc.i"
%flc_add_header

%{
#include <algorithm>
#include <functional>
#include <numeric>
%}

/* -------------------------------------------------------------------------
 * Macros
 * ------------------------------------------------------------------------- */
%define %flc_cmp_algorithm(RETURN_TYPE, FUNCNAME, ARGS, CALL)

%inline {
// Operate using default "less than"
template<class T>
static RETURN_TYPE FUNCNAME(ARGS) {
  return FUNCNAME##_impl(CALL, std::less<T>());
}
// Operate using user-provided function pointer
template<class T>
static RETURN_TYPE FUNCNAME##_cmp(ARGS, bool (*cmp)(T, T)) {
  return FUNCNAME##_impl(CALL, cmp);
}
}

// Instantiate numeric overloads
%flc_template_numeric(FUNCNAME, FUNCNAME)
%flc_template_numeric(FUNCNAME##_cmp, FUNCNAME)

// Instantiate comparators with void* arguments
%template(FUNCNAME) FUNCNAME##_cmp<void*>;

%enddef

/* ------------------------------------------------------------------------- */
%define %flc_typemaps(NAME, TYPE...)

// Apply array conversion typemap
%apply (const SWIGTYPE *DATA, size_t SIZE) {
    (TYPE const *DATA1, size_t DATASIZE1),
    (TYPE const *DATA2, size_t DATASIZE2) };

// Explicitly declare function interface for callbacks
%fortrancallback("%s") flc_cmp_##NAME;
extern "C" bool flc_cmp_##NAME(TYPE left, TYPE right);

%enddef

/* -------------------------------------------------------------------------
 * Types
 * ------------------------------------------------------------------------- */

// Alias the native C integer to an "indexing" integer returned by algorithm
// functions.
%inline %{
typedef int index_int;
%}
%insert("fdecl") %{integer, parameter, public :: INDEX_INT = C_INT
%}

// Give it a particularly named type in the Fortran proxy code.
%apply int { index_int };
%typemap(ftype, in="integer(INDEX_INT), intent(in)") index_int
  %{integer(INDEX_INT)%}

// Apply array-to-C translation for numeric values
%apply (SWIGTYPE *DATA, size_t SIZE) { (index_int *IDX, size_t IDXSIZE) };

// Apply array and callback typemaps
%flc_typemaps(int4 , int32_t   )
%flc_typemaps(int8 , int64_t   )
%flc_typemaps(real8, double    )
%flc_typemaps(index, index_int )
%flc_typemaps(ptr  , void*     )

/* -------------------------------------------------------------------------
 * Sorting routines
 * ------------------------------------------------------------------------- */

%{
template<class T, class Compare>
static void sort_impl(T *data, size_t size, Compare cmp) {
    return std::sort(data, data + size, cmp);
}

template<class T, class Compare>
static bool is_sorted_impl(const T *data, size_t size, Compare cmp) {
    return std::is_sorted(data, data + size, cmp);
}

template<class T, class Compare>
static void argsort_impl(const T *data, size_t size,
                         index_int *index, size_t index_size,
                         Compare cmp) {
  // Fill invalid indices with zero
  if (size < index_size) {
    std::fill(index + size, index + index_size, 0);
  }
  size = std::min(size, index_size);
  // Fill the indices with 1 through size
  std::iota(index, index + size, 1);
  // Define a comparator that accesses the original data
  auto int_sort_cmp = [cmp, data](index_int left, index_int right)
  { return cmp(data[left - 1], data[right - 1]); };
  // Let the standard library do all the hard work!
  std::sort(index, index + size, int_sort_cmp);
}

%}

%flc_cmp_algorithm(void, sort,
                   %arg(T *DATA, size_t DATASIZE),
                   %arg(DATA, DATASIZE))
%flc_cmp_algorithm(bool, is_sorted,
                   %arg(const T *DATA, size_t DATASIZE),
                   %arg(DATA, DATASIZE))
%flc_cmp_algorithm(void, argsort,
                   %arg(const T *DATA, size_t DATASIZE,
                        index_int *IDX, size_t IDXSIZE),
                   %arg(DATA, DATASIZE, IDX, IDXSIZE))

/* -------------------------------------------------------------------------
 * Searching routines
 * ------------------------------------------------------------------------- */

%{
template<class T, class Compare>
static index_int binary_search_impl(const T *data, size_t size, T value,
                                    Compare cmp) {
  const T *end = data + size;
  auto iter = std::lower_bound(data, end, value, cmp);
  if (iter == end || cmp(*iter, value) || cmp(value, *iter))
    return 0;
  // Index of the found item *IN FORTAN INDEXING*
  return (iter - data) + 1;
}

template<class T, class Compare>
static void equal_range_impl(const T *data, size_t size, T value,
                             index_int &first_index, index_int &last_index,
                             Compare cmp) {
  const T *end = data + size;
  auto range_pair = std::equal_range(data, end, value, cmp);
  // Index of the min/max items *IN FORTAN INDEXING*
  first_index = range_pair.first - data + 1;
  last_index = range_pair.second - data;
}

template<class T, class Compare>
static void minmax_element_impl(const T *data, size_t size,
                                index_int &min_index, index_int &max_index,
                                Compare cmp) {
  const T *end = data + size;
  auto mm_pair = std::minmax_element(data, end, cmp);
  // Index of the min/max items *IN FORTAN INDEXING*
  min_index = mm_pair.first - data + 1;
  max_index = mm_pair.second - data + 1;
}
%}

%flc_cmp_algorithm(index_int, binary_search,
                   %arg(const T *DATA, size_t DATASIZE, T value),
                   %arg(DATA, DATASIZE, value))

%flc_cmp_algorithm(void, equal_range,
                   %arg(const T *DATA, size_t DATASIZE, T value,
                        index_int &first_index, index_int &last_index),
                   %arg(DATA, DATASIZE, value, first_index, last_index))

%flc_cmp_algorithm(void, minmax_element,
                   %arg(const T *DATA, size_t DATASIZE,
                        index_int &min_index, index_int &max_index),
                   %arg(DATA, DATASIZE, min_index, max_index))

/* -------------------------------------------------------------------------
 * Set operation routines
 * ------------------------------------------------------------------------- */

%{
template<class T, class Compare>
static bool includes_impl(const T *data1, size_t size1,
                          const T *data2, size_t size2,
                          Compare cmp) {
  return std::includes(data1, data1 + size1, data2, data2 + size2, cmp);
}
%}

%flc_cmp_algorithm(bool, includes,
                   %arg(const T *DATA1, size_t DATASIZE1,
                        const T *DATA2, size_t DATASIZE2),
                   %arg(DATA1, DATASIZE1, DATA2, DATASIZE2))

/* -------------------------------------------------------------------------
 * Modifying routines
 * ------------------------------------------------------------------------- */

%{
#include <random>
%}

%import "flc_random.i"

%inline {
template<class T>
static void shuffle(std::FLC_DEFAULT_ENGINE& g, T *DATA, size_t DATASIZE) {
    std::shuffle(DATA, DATA + DATASIZE, g);
}
}

%flc_template_numeric(shuffle, shuffle)
%template(shuffle) shuffle<void*>;

flc_chrono

 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
/*!
 * \file flc_chrono.i
 *
 * Copyright (c) 2019 Oak Ridge National Laboratory, UT-Battelle, LLC.
 * Distributed under an MIT open source license: see LICENSE for details.
 */

%module "flc_chrono"
%include "import_flc.i"
%flc_add_header

/* -------------------------------------------------------------------------
 * Utility routines
 * ------------------------------------------------------------------------- */

%{
#include <chrono>
#include <thread>
#include <stdexcept>
%}

%inline %{
static void sleep_for(int ms) {
    if (ms < 0)
        throw std::domain_error("Invalid sleep time");
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
%}

flc_random

  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
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/*!
 * \file flc_random.i
 *
 * Copyright (c) 2019 Oak Ridge National Laboratory, UT-Battelle, LLC.
 * Distributed under an MIT open source license: see LICENSE for details.
 */

%module "flc_random"
%include "import_flc.i"
%flc_add_header

%{
#include <random>
#if defined(_MSC_VER) && _MSC_VER < 1900
// Visual studio 2012's standard library lacks iterator constructors for
// std::discrete_distribution
#define FLC_MISSING_DISCRETE_ITER
#endif
%}

/* -------------------------------------------------------------------------
 * Macros
 * ------------------------------------------------------------------------- */

%define %flc_random_engine(NAME, GENERATOR, RESULT_TYPE)
namespace std {

%rename(NAME) GENERATOR;
%rename("next") GENERATOR::operator();

class GENERATOR
{
  public:
    typedef RESULT_TYPE result_type;

    GENERATOR();
    explicit GENERATOR(result_type seed_value);
    void seed(result_type seed_value);
    void discard(unsigned long long count);
    result_type operator()();
};

} // namespace std
%enddef

/* -------------------------------------------------------------------------
 * RNG distribution routines
 * ------------------------------------------------------------------------- */

%{
template<class D, class G, class T>
static inline void flc_generate(D dist, G& g, T* data, size_t size) {
  T* const end = data + size;
  while (data != end) {
    *data++ = dist(g);
  }
}
%}

%apply (const SWIGTYPE *DATA, size_t SIZE) {
       (const double *WEIGHTS, size_t WEIGHTSIZE) };

%inline %{
template<class T, class G>
static void uniform_int_distribution(T left, T right,
                                     G& engine, T* DATA, size_t DATASIZE) {
  flc_generate(std::uniform_int_distribution<T>(left, right),
               engine, DATA, DATASIZE);
}

template<class T, class G>
static void uniform_real_distribution(T left, T right,
                                      G& engine, T* DATA, size_t DATASIZE) {
  flc_generate(std::uniform_real_distribution<T>(left, right),
               engine, DATA, DATASIZE);
}

template<class T, class G>
static void normal_distribution(T mean, T stddev,
                                G& engine, T* DATA, size_t DATASIZE) {
  flc_generate(std::normal_distribution<T>(mean, stddev),
               engine, DATA, DATASIZE);
}

template<class T, class G>
static void discrete_distribution(const double* WEIGHTS, size_t WEIGHTSIZE,
                                  G& engine, T* DATA, size_t DATASIZE) {
#ifndef FLC_MISSING_DISCRETE_ITER
  std::discrete_distribution<T> dist(WEIGHTS, WEIGHTS + WEIGHTSIZE);
#else
  std::discrete_distribution<T> dist(
      std::initializer_list<double>(WEIGHTS, WEIGHTS + WEIGHTSIZE));
#endif
  T* const end = DATA + DATASIZE;
  while (DATA != end) {
    *DATA++ = dist(engine) + 1; // Note: transform to Fortran 1-offset
  }
}
%}

%define %flc_distribution(NAME, STDENGINE, TYPE)
%template(NAME##_distribution) NAME##_distribution< TYPE, std::STDENGINE >;
%enddef

// Engines
%flc_random_engine(MersenneEngine4, mt19937,    int32_t)
%flc_random_engine(MersenneEngine8, mt19937_64, int64_t)

#define FLC_DEFAULT_ENGINE mt19937
%flc_distribution(uniform_int,  FLC_DEFAULT_ENGINE, int32_t)
%flc_distribution(uniform_int,  FLC_DEFAULT_ENGINE, int64_t)
%flc_distribution(uniform_real, FLC_DEFAULT_ENGINE, double)

%flc_distribution(normal, FLC_DEFAULT_ENGINE, double)

// Discrete sampling distribution
%flc_distribution(discrete, FLC_DEFAULT_ENGINE, int32_t)
%flc_distribution(discrete, FLC_DEFAULT_ENGINE, int64_t)

flc_set

  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
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*!
 * \file flc_set.i
 *
 * Copyright (c) 2019 Oak Ridge National Laboratory, UT-Battelle, LLC.
 * Distributed under an MIT open source license: see LICENSE for details.
 */

%module "flc_set"
%include "import_flc.i"
%flc_add_header

%include <std_set.i>

// Support for set operations
%{
#include <algorithm>
#include <iterator>
%}

/* -------------------------------------------------------------------------
 * Macro definitions
 * ------------------------------------------------------------------------- */

%define %flc_define_set_algorithm(FUNCNAME)
  %insert("header") {
  template<class Set_t>
  static Set_t flc_##FUNCNAME(const Set_t& left, const Set_t& right)
  {
      Set_t result;
      std::FUNCNAME(left.begin(), left.end(),
                    right.begin(), right.end(),
                    std::inserter(result, result.end()));
      return result;
  }
  } // end %insert
%enddef

%define %flc_extend_set_algorithm(FUNCNAME, RETVAL, TYPE)
  // The rename with the stringifying macro is necessary because 'union' is a
  // keyword.
  %rename(#FUNCNAME) std::set<TYPE>::set_##FUNCNAME;
  %extend std::set<TYPE> {
   RETVAL set_##FUNCNAME(const std::set<TYPE>& other)
   { return flc_set_##FUNCNAME(*$self, other); }
  } // end %extend
%enddef

%define %flc_std_set_extend_pod(CTYPE)
%extend {
  %apply (const SWIGTYPE *DATA, ::size_t SIZE)
    { (const CTYPE* DATA, size_type SIZE) };

  // Construct from an array of data
  set(const CTYPE* DATA, size_type SIZE) {
    return new std::set<CTYPE>(DATA, DATA + SIZE);
  }

  // Insert an array of data
  void insert(const CTYPE* DATA, size_type SIZE) {
    $self->insert(DATA, DATA + SIZE);
  }
}
%enddef

/* ------------------------------------------------------------------------- */
/*! \def %specialize_std_set_pod
 *
 * Inject member functions and typemaps for POD classes.
 *
 * These provide an efficient constructor from a Fortan array view.
 *
 * This definition is considered part of the \em public API so that downstream
 * apps that generate FLC-based bindings can instantiate their own POD sets.
 */
%define %specialize_std_set_pod(T)

namespace std {
  template<> class set<T> {
    %swig_std_set(T, std::less<T>, std::allocator<T>)
    %flc_std_set_extend_pod(T)
  };
}
%enddef

/* -------------------------------------------------------------------------
 * Algorithms
 * ------------------------------------------------------------------------- */

%flc_define_set_algorithm(set_difference)
%flc_define_set_algorithm(set_intersection)
%flc_define_set_algorithm(set_symmetric_difference)
%flc_define_set_algorithm(set_union)

%insert("header") %{
template<class Set_t>
static bool flc_set_includes(const Set_t& left, const Set_t& right)
{
    return std::includes(left.begin(), left.end(),
                         right.begin(), right.end());
}
%}

%define %flc_extend_algorithms(TYPE)
  %flc_extend_set_algorithm(difference, std::set<TYPE >, TYPE)
  %flc_extend_set_algorithm(intersection, std::set<TYPE >, TYPE)
  %flc_extend_set_algorithm(symmetric_difference, std::set<TYPE >, TYPE)
  %flc_extend_set_algorithm(union, std::set<TYPE >, TYPE)
  %flc_extend_set_algorithm(includes, bool, TYPE)
%enddef

/* -------------------------------------------------------------------------
 * Numeric sets
 * ------------------------------------------------------------------------- */

%flc_extend_algorithms(int)
%specialize_std_set_pod(int)

%template(SetInt) std::set<int>;

/* -------------------------------------------------------------------------
 * String sets
 * ------------------------------------------------------------------------- */

// Allow direct insertion of a wrapped std::string
%extend std::set<std::string> {
  %apply SWIGTYPE& { const std::string& STR_CLASS };

  void insert_ref(const std::string& STR_CLASS) {
    $self->insert(STR_CLASS);
  }
}

%include <std_string.i>
%import "flc_string.i"
%flc_extend_algorithms(std::string)
%template(SetString) std::set<std::string>;

flc_string

  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
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*!
 * \file flc_string.i
 *
 * Copyright (c) 2019 Oak Ridge National Laboratory, UT-Battelle, LLC.
 * Distributed under an MIT open source license: see LICENSE for details.
 */

%module "flc_string"
%include "import_flc.i"
%flc_add_header

// SWIG always represents std::string as native strings. We load its typemaps
// but will explicitly create the class.
%include <std_string.i>

// Include typemaps for integer offsets and native integer types
%include <std_common.i>

/* -------------------------------------------------------------------------
 * Typemaps
 * ------------------------------------------------------------------------- */

// Typemap to convert positions from npos -> 0 and 1-offset otherwise. Similar
// to
%apply int FORTRAN_INT { size_t POSITION };
%typemap(out, noblock=1) size_t POSITION {
  $result = ($1 == std::string::npos ? 0 : $1 + 1);
}

/* -------------------------------------------------------------------------
 * String class definition
 * ------------------------------------------------------------------------- */

namespace std {
class string {
  public:
    // >>> TYPES
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef char value_type;
    typedef const char& const_reference;

    // Typemaps for making std::vector feel more like native Fortran:
    // - Use Fortran 1-offset indexing
    %apply int FORTRAN_INDEX {size_type pos,
                              size_type index,
                              size_type start_index,
                              size_type stop_index};
    // - Use native Fortran integers in proxy code
    %apply int FORTRAN_INT {size_type};

    // - Use fortran indexing (and 0 for not found) for search
    %apply size_t POSITION {size_type find};

    // - Allow access as an array view
    %apply SWIGTYPE& { string& view };
    %fortran_array_pointer(char, string& view);
    %typemap(out, noblock=1) string& view {
      $result.data = ($1->empty() ? NULL : const_cast<char*>($1->data()));
      $result.size = $1->size();
    }

    // - Allow interaction with other string objects
    %apply SWIGTYPE& {const string& OTHER};

  public:
    // >>> MEMBER FUNCTIONS

    string();
    string(size_type count, value_type ch);
    string(const std::string& s);

    // Accessors
    size_type size() const;
    bool empty() const;

    const_reference front() const;
    const_reference back() const;

    // Modify
    void resize(size_type count);
    void resize(size_type count, value_type v);
    void assign(const string& s);
    void push_back(value_type v);
    void pop_back();
    void clear();

    // String operations
    size_type find(const string& s, size_type pos = 0);
    void append(const string& s);
    int compare(const string& OTHER);

    // >>> EXTENSIONS

    %extend {
      %fragment("SWIG_check_range");

      void set(size_type index, value_type v) {
        SWIG_check_range(index, $self->size(),
                         "std::string::set",
                         return);
        (*$self)[index] = v;
      }

      value_type get(size_type index) {
        SWIG_check_range(index, $self->size(),
                         "std::string::get",
                         return $self->front());
        return (*$self)[index];
      }

      // Get a character array view
      string& view() { return *$self; }

      // Get a copy as a native Fortran string
      const string& str() { return *$self; }
    }
};

/* -------------------------------------------------------------------------
 * String conversion routines
 * ------------------------------------------------------------------------- */

%exception {
  SWIG_check_unhandled_exception();
  try {
    $action
  }
  catch (const std::invalid_argument& e) {
    SWIG_exception(SWIG_ValueError, e.what());
  }
  catch (const std::out_of_range& e) {
    SWIG_exception(SWIG_OverflowError, e.what());
  }
}

%fragment("<cctype>", "header") %{
#include <cctype>
%}

%fragment("flc_has_junk", "header",
          fragment="<cctype>", fragment="<algorithm>") %{
  SWIGINTERN bool flc_has_junk(const std::string& s, size_t pos) {
    return !std::all_of(s.begin() + pos, s.end(),
                        [](unsigned char c) -> bool { return std::isspace(c); });
  }
%}

%typemap(in, numinputs=0, noblock=1) size_t* result_pos (size_t temp_pos) {
  temp_pos = 0;
  $1 = &temp_pos;
}
%typemap(argout, noblock=1, fragment="flc_has_junk") size_t* result_pos {
  if (flc_has_junk(*arg1, temp_pos$argnum)) {
    SWIG_exception(SWIG_ValueError, "Junk at end of string");
  }
}

// String conversion routines
#define %add_string_int_conversion(RETURN_TYPE, NAME) \
  RETURN_TYPE NAME(const string& s, size_t* result_pos, int base = 10)
#define %add_string_real_conversion(RETURN_TYPE, NAME) \
  RETURN_TYPE NAME(const string& s, size_t* result_pos)

%add_string_int_conversion(int, stoi);
%add_string_int_conversion(long, stol);
%add_string_int_conversion(long long, stoll);
%add_string_real_conversion(float, stof);
%add_string_real_conversion(double, stod);

// Don't add exception code for subsequent functions
%exception;

} // namespace std

flc_vector

  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
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*!
 * \file flc_vector.i
 *
 * Copyright (c) 2019-2020 Oak Ridge National Laboratory, UT-Battelle, LLC.
 * Distributed under an MIT open source license: see LICENSE for details.
 */

%module "flc_vector"
%include "import_flc.i"
%flc_add_header

%include <complex.i>
%include <std_vector.i>

/* -------------------------------------------------------------------------
 * Macro definitions
 * ------------------------------------------------------------------------- */

%define %flc_std_vector_extend_pod(CTYPE, IMTYPE)
%extend {
  %apply (const SWIGTYPE *DATA, ::size_t SIZE)
    { (const CTYPE* DATA, size_type SIZE) };

  // Construct from an array of data
  vector(const CTYPE* DATA, size_type SIZE) {
    return new std::vector<CTYPE>(DATA, DATA + SIZE);
  }

  // Assign from another vector
  void assign(const CTYPE* DATA, size_type SIZE) {
    $self->assign(DATA, DATA + SIZE);
  }

  // Get a mutable view to ourself
  %fortran_array_pointer(IMTYPE, vector<CTYPE>& view);

  %typemap(out, noblock=1) vector<CTYPE>& view {
    $result.data = ($1->empty() ? NULL : &(*$1->begin()));
    $result.size = $1->size();
  }

  vector<CTYPE>& view() {
    return *$self;
  }
}
%enddef

/* ------------------------------------------------------------------------- */
/*! \def %flc_template_std_vector_pod
 *
 * Inject member functions and typemaps for POD classes, and instantiate.
 *
 * The added methods provide an efficient constructor from a Fortan array view.
 * It also offers a "view" functionality for getting an array pointer to the
 * vector-owned data.
 *
 * This definition is considered part of the \em public API so that downstream
 * apps that generate FLC-based bindings can instantiate their own POD vectors.
 */
%define %flc_template_std_vector_pod(NAME, T)

namespace std {
  template<> class vector<T> {

    %swig_std_vector(T, const T&)
    %swig_std_vector_extend_ref(T)
    %flc_std_vector_extend_pod(T, T)
  };
}

// Instantiate the template
%template(NAME) std::vector<T>;

%enddef


/* ------------------------------------------------------------------------- */
/*! \def %flc_template_std_vector_complex
 *
 * Inject member functions and typemaps for std::complex instantiations.
 *
 * This definition is considered part of the \em public API so that downstream
 * apps that generate FLC-based bindings can instantiate their own POD vectors.
 */
%define %flc_template_std_vector_complex(NAME, T)

namespace std {
  template<> class vector<std::complex<T> > {

    %swig_std_vector(std::complex<T>, const std::complex<T>&)
    %swig_std_vector_extend_ref(std::complex<T>)
    %flc_std_vector_extend_pod(std::complex<T>, SwigComplex_##T)
  };
}

// Instantiate the template
%template(NAME) std::vector<std::complex<T> >;

%enddef

/* -------------------------------------------------------------------------
 * Numeric vectors
 * ------------------------------------------------------------------------- */

%flc_template_std_vector_pod(VectorInt4,  int32_t)
%flc_template_std_vector_pod(VectorInt8,  int64_t)
%flc_template_std_vector_pod(VectorReal8, double)

%flc_template_std_vector_complex(VectorComplex8, double)

/* -------------------------------------------------------------------------
 * String vectors
 * ------------------------------------------------------------------------- */

%include <std_string.i>
%import "flc_string.i"

%apply SWIGTYPE& { const std::string& value };

%extend std::vector<std::string> {
  void set_ref(size_type index, const std::string& value) {
    SWIG_check_range(index, $self->size(),
                     "std::vector<std::string>::set_ref",
                     return);
    (*$self)[index] = value;
  }
}

%template(VectorString) std::vector<std::string>;

%clear const std::string& value;

License

MIT License

Copyright (c) 2019–2021 Oak Ridge National Laboratory, UT–Battelle, LLC.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.