I'm still writing this summary and would be interested in any feedback you might offer.

Using C++ Trait Classes for Scientific Computing

Todd Veldhuizen, 3/30/96

Trait classes [Myers'95] solve a number of problems which arise in the design of C++ template libraries for scientific computing. They provide mappings from C++ types onto other types, data, and code. An example of a trait class is numeric_limits<T>, provided by the ANSI/ISO C++ Standard. The numeric_limits<T> trait class provides information about the numerical properties of C++ types such as int and float. In the ANSI C standard, these properties were provided by constants such as FLT_MIN, FLT_MAX and FLT_EPSILON (which represent the min, max and epsilon values for float). Although these constants remain in the C++ standard, they are not useful when writing templates, since one does not know whether to use INT_MIN, FLT_MIN or DBL_MIN. The numeric_limits<T> class solves this problem.

Consider a function template to find the largest element in an array. One approach is to set a temporary variable equal to a large negative value, and then loop through the array, updating the temporary variable whenever the current element is larger. If this function were to handle only type float, then we could initialize the temporary variable to FLT_MIN (the largest negative value which can be represented by a float). However, since we want our function template to handle any numerical type, we can use numeric_limits<T> to access the appropriate minimum value for the template parameter T:

template<class T>
T findMax(const T* data, int numItems)
    // Obtain the minimum value for type T
    T largest = numeric_limits<T>::min();

    for (int i=0; i < numItems; ++i)
        if (data[i] > largest)
            largest = data[i];

    return largest;

The numeric_limits<T> class provides a wealth of information about a numeric data type: its minimum and maximum values; how many binary and decimal digits can be considered accurate; whether it is signed; whether it is integer-valued; the radix of its representation (generally base 2); the machine epsilon; for floating point types, the range of exponents in base 2 and base 10; and details about rounding behaviour.

Having this type of information available makes it simple to write template numerical functions whose behaviour depends on the type of number representation being used.

Trait classes also simplify the implementation of arithmetic type promotion for vectors, allow a class library to track structural changes in matrices resulting from matrix operations, and can be used to simplify the interface to classes with many template parameters. The following sections summarize these uses.

Arithmetic Type promotions for Vectors, Matrices, and Arrays

Consider a Vector<T> class which provides a vector of numbers. It can be instantiated using any C++ numerical type:

Vector<double>           double_vec;     // A double-precision vector
Vector<int>              int_vec;        // An integer vector
Vector<complex<double> > complex_vec;    // A complex vector

What should happen when double_vec is added to complex_vec? Clearly the result should be a complex vector; otherwise the imaginary component of complex_vec would be lost. On the other hand, if int_vec is added to double_vec, the result should be a double-precision vector.

Ideally, we would implement C-style arithmetic type promotions; when vectors of two different types are added, the result should be promoted to whatever type results in the least loss of precision. One way of implementing this would be to provide specialized versions of operator+ to handle all the cases, for example:

Vector<double> operator+(const Vector<double>& a, const Vector<int>& b)

This is clearly a tedious solution, quickly defeated by combinatorics: the specializations required to handle all combinations of type and operator number in the hundreds.

A trait class provides a better solution. Define a template class ``promote_trait'' which takes two template parameters T1 and T2. Inside the class, a publicly accessible type ``T_promote'' is declared to be the appropriate type promotion for an arithmetic operation on types T1 and T2. This is done through template specialization:

template<class T1, class T2>
struct promote_trait {
    typedef T1 T_promote;      // Default is to promote to type T1

// Specialize for the <int, double> case
struct promote_trait<int, double> {
    typedef double T_promote;

// Specialize for the <double, complex<double> > case
struct promote_trait<double, complex<double> > {
    typedef complex<double> T_promote;

In practice, one would write a program to automatically generate all the appropriate specializations of a class such as promote_trait<T1,T2>. Here is a sample implementation.

Using this trait class, we need only provide a single version of operator+. The result will be automatically promoted to the appropriate type:

template<class T1, class T2>
operator+(const Vector<T1>& a, const Vector<T2>& b)

Matrix structure promotions

There are many matrix structures which the C++ class library developer would like to support. To name a few: Dense, Diagonal, Tridiagonal, Banded, Upper triangular, Lower triangular, Symmetric, Toeplitz, and Hermitian. These structures require different storage schemes. Rather than providing a separate class to implement each structure, one can define ``structure classes'' which encapsulate information about the storage scheme. This structure class can then be passed as a template parameter to a generic matrix object:
Matrix<double, Diagonal>    A;       // diagonal matrix
Matrix<double, Tridiagonal> B;       // tridiagonal matrix
Matrix<float, Dense>        C;       // dense matrix

When operations are performed on matrices, arithmetic type promotions can be handled using the promote_trait<T> class discussed previously. This will ensure that adding a float matrix to a double matrix results in a double matrix, for example.

Another promotion problem is presented by the matrix structures. Adding A+B results in a matrix with tridiagonal structure. Adding B+C results in a matrix with dense structure. Matrix multiplication also results in structure changes: multiplying A and B results in a tridiagonal matrix, but muliplying B by itself results in a banded matrix (with bandwidth 5).

These structure promotions can be handled similarly to arithmetic type promotions. Two trait classes are required-- one for matrix structure promotions under addition and subtraction, and the other for structure promotions under multiplication.

Interface Simplification

(This section needs to be rewritten in light of changes to the ANSI/ISO C++ standard which allow templates as template arguments.)


  1. The idea of the numeric_limits<T> trait class is due to John Barton and Lee Nackman, who published a similar class in their book, ``Scientific and Engineering C++''. It was subsequently reinvented by the author and included as part of the ANSI/ISO C++ standard.
  2. See Nathan Myer's article: Traits: a new and useful template technique (C++ Report, June 1995)
  3. The type tag ideas were inspired by John Vriezen. He wrote an article which I am trying to track down.

Web Site

More information on these techniques is available from the Blitz++ Project Home Page, at "http://monet.uwaterloo.ca/blitz/".

How to reach me:


Todd Veldhuizen
Dept. of Systems Design Engineering
University of Waterloo, Waterloo, Ontario
Canada. N2L 3G1
Tel: (519) 885-1211 ext. 6087
Fax: (519) 746-3077

Last Revision: March 31, 1996