Выбрать главу

Now consider what would happen if the comment were removed from the last line of main( ):

   char c = safe_cast<char>(p);

In this case the STATIC_CHECK( ) macro inside safe_cast<char>(p) expands to:

{                                     \

   class Error_NarrowingConversion{}; \

   sizeof(StaticCheck<sizeof(void*) <= sizeof(char)> \

           (Error_NarrowingConversion()));           \

}

Since the expression sizeof(void*) <= sizeof(char) is false, a conversion from an Error_NarrowingConversion temporary to StaticCheck<false> is attempted, as follows:

sizeof(StaticCheck<false>(Error_NarrowingConversion()));

which fails, so the compiler halts with a message something like the following:.

Cannot cast from 'Error_NarrowingConversion' to 'StaticCheck<0>' in function

char safe_cast<char,void *>(void *)

The class name Error_NarrowingConversion is the meaningful message judiciously arrange by the coder. In general, to perform a static assertion with this technique, you just invoke the STATIC_CHECK macro with the compile-time condition to check and with a meaningful name to describe the error.

Expression templates

Perhaps the most powerful application of templates is a technique discovered independently in 1994 by Todd Veldhuizen[72] and David Vandevoorde:[73] expression templates. Expression templates enable extensive compile-time optimization of certain computations that results in code that is at least as fast as hand-optimized Fortran, and yet preserves the natural notation of mathematics via operator overloading. Although you wouldn’t be likely to use this technique in everyday programming, it is the basis for a number of sophisticated, high-performance mathematical libraries written in C++.[74] 

To motivate the need for expression templates, consider typical numerical linear algebra operations, such as adding together two matrices or vectors,[75] such as in the following:

D = A + B + C;

In naive implementations, this expression would result in a number of temporaries—one for A+B, and one for (A+B)+C. When these variables represent immense matrices or vectors, the coincident drain on resources is unacceptable. Expression templates allow you to use the same expression without temporaries.

In the following sample program, we define a MyVector class to simulate mathematical vectors of any size. We use a non-type template argument for the length of the vector. We also define a MyVectorSum class to act as a proxy class for a sum of MyVector objects. This allows us to use lazy evaluation, so the addition of vector components is performed on demand without the need for temporaries.

//: C05:MyVector.cpp

// Optimizes away temporaries via templates

#include <cstddef>

#include <cstdlib>

#include <iostream>

using namespace std;

// A proxy class for sums of vectors

template<class, size_t> class MyVectorSum;

template<class T, size_t N>

class MyVector {

  T data[N];

public:

  MyVector<T,N>& operator=(const MyVector<T,N>& right) {

    for (size_t i = 0; i < N; ++i)

      data[i] = right.data[i];

    return *this;

  }

  MyVector<T,N>& operator=(const MyVectorSum<T,N>& right);

  const T& operator[](size_t i) const {

    return data[i];

  }

  T& operator[](size_t i) {

    return data[i];

  }

};

// Proxy class hold references; uses lazy addition

template <class T, size_t N>

class MyVectorSum {

  const MyVector<T,N>& left;

  const MyVector<T,N>& right;

public:

  MyVectorSum(const MyVector<T,N>& lhs,

              const MyVector<T,N>& rhs)

      : left(lhs), right(rhs) {}

  T operator[](size_t i) const {

    return left[i] + right[i];

  }

};

// Operator to support v3 = v1 + v2

template<class T, size_t N>

MyVector<T,N>&

MyVector<T,N>::operator=(const MyVectorSum<T,N>& right) {

  for (size_t i = 0; i < N; ++i)

    data[i] = right[i];

  return *this;

}

// operator+ just stores references

template<class T, size_t N>

inline MyVectorSum<T,N>

operator+(const MyVector<T,N>& left,

          const MyVector<T,N>& right) {

  return MyVectorSum<T,N>(left, right);

}

// Convenience functions for the test program below

template<class T, size_t N>

void init(MyVector<T,N>& v) {

  for (size_t i = 0; i < N; ++i)

    v[i] = rand() % 100;

}

template<class T, size_t N>

void print(MyVector<T,N>& v) {

  for (size_t i = 0; i < N; ++i)

    cout << v[i] << ' ';

  cout << endl;

}

int main() {

  MyVector<int, 5> v1;

  init(v1);

  print(v1);

  MyVector<int, 5> v2;

  init(v2);

  print(v2);

  MyVector<int, 5> v3;

  v3 = v1 + v2;

  print(v3);

  MyVector<int, 5> v4;

  // Not yet supported:

//!  v4 = v1 + v2 + v3;

} ///:~.

The MyVectorSum class does no computation when it is created; it merely holds references to the two vectors to be added. It is only when you access a component of a vector sum that it is calculated (see its operator[]( )). The overload of the assignment operator for MyVector that takes a MyVectorSum argument is for an expression such as:.