v1 = v2 + v3; // add two vectors
When the expression v1+v2 is evaluated, a MyVectorSum object is returned (or actually, inserted inline, since that operator+( ) is declared inline). This is a small, fixed-size object (it holds only two references). Then the assignment operator mentioned above is invoked:
v3.operator=<int,5>(MyVectorSum<int,5>(v2, v3));
This assigns to each element of v3 the sum of the corresponding elements of v1 and v2, computed in real time. No temporary MyVector objects are created.
This program does not support an expression that has more than two operands, however, such as
v4 = v1 + v2 + v3;
The reason is that after the first addition, a second addition is attempted:
(v1 + v2) + v3;
which would require an operator+( ) with a first argument of MyVectorSum and a second argument of type MyVector. We could attempt to provide a number of overloads to meet all situations, but it is better to let templates do the work, as in the following version of the program.
//: C05:MyVector2.cpp
// Handles sums of any length with expression templates
#include <cstddef>
#include <cstdlib>
#include <iostream>
using namespace std;
// A proxy class for sums of vectors
template<class, size_t, class, class> 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;
}
template<class Left, class Right>
MyVector<T,N>&
operator=(const MyVectorSum<T,N,Left,Right>& right);
const T& operator[](size_t i) const {
return data[i];
}
T& operator[](size_t i) {
return data[i];
}
};
// Allows mixing MyVector and MyVectorSum
template <class T, size_t N, class Left, class Right>
class MyVectorSum {
const Left& left;
const Right& right;
public:
MyVectorSum(const Left& lhs, const Right& rhs)
: left(lhs), right(rhs) {}
T operator[](size_t i) const {
return left[i] + right[i];
}
};
template<class T, size_t N>
template<class Left, class Right>
MyVector<T,N>&
MyVector<T,N>::
operator=(const MyVectorSum<T,N,Left,Right>& 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,MyVector<T,N>,MyVector<T,N> >
operator+(const MyVector<T,N>& left,
const MyVector<T,N>& right) {
return
MyVectorSum<T,N,MyVector<T,N>,MyVector<T,N> >
(left,right);
}
template<class T, size_t N, class Left, class Right>
inline
MyVectorSum<T, N, MyVectorSum<T,N,Left,Right>,
MyVector<T,N> >
operator+(const MyVectorSum<T,N,Left,Right>& left,
const MyVector<T,N>& right) {
return MyVectorSum<T,N,MyVectorSum<T,N,Left,Right>,
MyVector<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);
// Now supported:
MyVector<int, 5> v4;
v4 = v1 + v2 + v3;
print(v4);
MyVector<int, 5> v5;
v5 = v1 + v2 + v3 + v4;
print(v5);
} ///:~.
Instead of committing ahead of time which types the arguments of a sum will be, we let the template facility deduce them with the template arguments, Left and Right. The MyVectorSum template takes these extra two parameters so it can represent a sum of any combination of pairs of MyVector and MyVectorSum. Note also that the assignment operator this time is a member function template. This also allows any <T, N> pair to be coupled with any <Left, Right> pair, so a MyVector object can be assigned from a MyVectorSum holding references to any possible pair of the types MyVector and MyVectorSum. As we did before, let’s trace through a sample assignment to understand exactly what takes place, beginning with the expression.
v4 = v1 + v2 + v3;
Since the resulting expressions become quite unwieldy, in the explanation that follows, we will use MVS as shorthand for MyVectorSum, and will omit the template arguments.
The first operation is v1+v2, which invokes the inline operator+( ), which in turn inserts MVS(v1, v2) into the compilation stream. This is then added to v3, which results in a temporary object according to the expression MVS(MVS(v1, v2), v3). The final representation of the entire statement is.
v4.operator+(MVS(MVS(v1, v2), v3));
This transformation is all arranged by the compiler and explains why this technique carries the moniker "expression templates"; the template MyVectorSum represents an expression (an addition, in this case), and the nested calls above are reminiscent of the parse tree of the left-associative expression v1+v2+v3.
An excellent article by Angelika Langer and Klaus Kreft explains how this technique can be extended to more complex computations.[76]