next up previous contents
Next: 6 Array Sharing Up: IMPL_repdoc Previous: 4 Inheritance   Contents

Subsections


5 Templates

Templating is a way of creating generic functions that perform the same operations on multiple kinds of data. Ideally the programmer only has to write the implementation once, and the language can determine or enable the programmer to specify the particular kind of data desired.

5.1 C++ Implementation

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.

5.2 F90 Implementation

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

5.3 C Implementation

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


next up previous contents
Next: 6 Array Sharing Up: IMPL_repdoc Previous: 4 Inheritance   Contents
esmf_support@list.woc.noaa.gov