5 Templates

Code for the example is in `template_example/C++`.

C++ has built-in templating that enables the programmer to define an operation based on one or more generic types. The type is only required to define the operations that are used in the templated procedure.

It was originally feared that wrapping C++ classes in F90 would seriously limit the ability to
use templates, but upon investigation we found that C++ templates may be used to
whatever extent desired. The slight inconvenience encountered is that for
all desired types the templated procedure must be known in advance
and coded into the interface. Still, the same implementation code can be used for multiple
template types: only the *interface* must be duplicated. This is a step beyond what
an implementation in F90 would grant; in other languages the code as well as the
interface would need to be duplicated for each desired type.

As an example to demonstrate both the power of templating and the method for wrapping templates in F90, the following example builds a generic polynomial class with some basic operations. The example is short and only provides a few operations for the sake of clarity. The reader can imagine extending this class to provide many more operations such as root finding, derivatives, etc...

The extraordinary thing about this example is that the code is written for a generic
algebraic type (`DTYPE`). When this is actually used the user must specify
the algebraic type. The only restriction on the type is that it implement the operations used
within the algorithm, namely`` +-/*''. This type could be `double, complex, int` or some
user defined type.

#include<iostream.h> #include <math.h> #include <complex.h> #include "ftn.h" template<class DTYPE> class poly { public: poly(int order_, DTYPE *coefficients_) { int i; order = order_; coefficients = new DTYPE[order + 1]; for (i = 0; i < order + 1; i++) coefficients[i] = coefficients_[i]; } ~poly() { delete [] coefficients; } void evaluate(DTYPE &x, DTYPE &res) { int i, j; // res = (DTYPE) 0; (this would require DTYPE to implement assignment from int). res -= res; for (i=0; i <= order; i++) { res *= x; res += coefficients[i]; } } void get_order(int &order_) { order_ = order; } // void findRoots(DTYPE *roots); // etc... private: DTYPE *coefficients; int order; };

An F90 module interface:

module polyMod type polyPtr private sequence integer(r8) :: this end type interface polyCreate module procedure polyCreateReal module procedure polyCreateInt end interface interface polyEvaluate module procedure polyEvaluateReal module procedure polyEvaluateInt end interface contains function polyCreateReal(order, coefficients) integer, intent(in) :: order real(r8) :: coefficients(:) type(polyPtr) :: polyCreateReal call c_poly_create_double(polyCreateReal, order, coefficients) end function function polyCreateInt(order, coefficients) integer, intent(in) :: order integer :: coefficients(:) type(polyPtr) :: polyCreateInt call c_poly_create_int(polyCreateInt, order, coefficients) end function subroutine polyGetOrder(this, order) type(polyPtr) :: this integer, intent(out) :: order call c_poly_get_order(this, order) end subroutine subroutine polyEvaluateReal(this, x, res) type(polyPtr) :: this real(r8) :: x real(r8), intent(out) :: res call c_poly_evaluate_double(this, x, res) end subroutine subroutine polyEvaluateInt(this, x, res) type(polyPtr) :: this integer :: x integer, intent(out) :: res call c_poly_evaluate_int(this, x, res) end subroutine subroutine polyDestroy(this) type(polyPtr) :: this call c_poly_destroy(this) end subroutine end module

There are significant similarities between this module interface and the previous inheritance example. The F90 derived type defines a handle, and each function maps to one of the C++ member functions.

There are some new things as well. In this example the two template parameters
implemented are `Int` and `Real`. A separate call for each routine
must be generated based on the type. The F90 `interface` feature nicely
maps these back to a single function for the module user.

The C ``glue'' code is now a bit more complex. Since writing a function that
uses a `poly<DTYPE>` pointer is no longer a real function but just a
function template, a separate call must be made to the template function for
each type that the interface supports:

// Generic interface functions template<class DTYPE> void c_poly_create(poly<DTYPE> **ths, int *order, DTYPE *coefficients) { *ths = new poly<DTYPE>(*order, coefficients); } template<class DTYPE> void c_poly_evaluate(poly<DTYPE> **ths, DTYPE *x, DTYPE *res) { (*ths)->evaluate(*x, *res); } extern "C" { // Order doesn't care about template type. Use int as placeholder. Could // use void*, but this class requires arithmetic operators. void FTN(c_poly_get_order)(poly<int> **ths, int *order) { (*ths)->get_order(*order); } void FTN(c_poly_destroy)(poly<int> **ths) { delete *ths; } // Now implement for double and int. Could be any user defined class. void FTN(c_poly_create_double)(poly<double> **ths, int *order, double *coefficients) { c_poly_create(ths, order, coefficients); } void FTN(c_poly_create_int)(poly<int> **ths, int *order, int *coefficients) { c_poly_create(ths, order, coefficients); } void FTN(c_poly_evaluate_double)(poly<double> **ths, double *x, double *res) { c_poly_evaluate(ths, x, res); } void FTN(c_poly_evaluate_int)(poly<int> **ths, int *x, int *res) { c_poly_evaluate(ths, x, res); } }

An F90 program using the class:

program polyEx Use polyMod type(polyPtr) :: poly1 type(polyPtr) :: poly2 real(r8) :: x, res integer :: order, intx, intres real(r8) :: coeff1(4) = (/ 4.4, 3.3, 2.2, 1.1 /) integer :: coeff2(3) = (/ 3, 2, 1 /) poly1 = polyCreate(3, coeff1) poly2 = polyCreate(2, coeff2) call polyGetOrder(poly1, order) print *, "order of poly1 is:", order call polyGetOrder(poly2, order) print *, "order of poly2 is:", order do x=-20., 20.,0.5 call polyEvaluate(poly1, x, res) intx = x call polyEvaluate(poly2, intx, intres) print *, "poly1(", x, ") = ", res, "poly2(", intx, ") = ", intres enddo end

Constructing the interface between the templated C++ class and the F90 module is similar to
the approach used in the inheritance example.
However, we can no longer use the trick of passing in our `this` pointer to the C++ class
as a base class
pointer. Now class pointers also have a template parameter that must be resolved, since there
is no way in C++ of representing a generic templated type as a pointer; the class has
to know the specify type to operate. Hence, we
define a set of interface functions that convert the `this` to a typed-pointer and pass the
arguments to
a member function call, and we create these as parameterized functions. These are not callable
yet by F90, because they are only function templates and not actual functions. To finalize
the link, we must create a true C function that calls the templated function with the specific
arguments that we are going to use (in this example, `double` and `int`).

Some functions within the templated class do not have any real reason to care what the template
type is (i.e. they do not use any instances of the type in their algorithm).
For instance, `get_order` merely returns an `int` specifying the order of the
polynomial. It does not use any `DTYPE` parameter. For these functions,
we do not need to explicity write out every templated type. We can use a place holder. So
here we create one interface function:

void FTN(c_poly_get_order)(poly<int> **ths, int *order)

This function is called for every type, but it still works since the `poly<int>::get_order`
funtion works just as well as the `poly<double>::get_order` function, or any other type,
including `void*`. Normally one would use `void*`, but this class requires that the
type have basic arithmetic operators and `void*` does not compile.

Also of interest is how the F90 interface mechanism fits nicely with the templating. For example, the function:

interface polyCreate module procedure polyCreateReal module procedure polyCreateInt end interface

automatically is dispatched in F90 to the appropriate type. Under the hood the C++ interface calls the corresponding template type for either double or int.

The FORTRAN language does not have templating. Therefore separate algorithms must be coded for every type used, or special-purpose macros written.

The C language does not have templating. Therefore separate algorithms must be coded for every type used, or special-purpose macros written.