inline MyVectorSum
operator+(const MyVector
const MyVector
return
MyVectorSum
(left,right);
}
template
inline
MyVectorSum
MyVector
operator+(const MyVectorSum
const MyVector
return MyVectorSum
MyVector
(left, right);
}
// Convenience functions for the test program below
template
void init(MyVector
for (size_t i = 0; i < N; ++i)
v[i] = rand() % 100;
}
template
void print(MyVector
for (size_t i = 0; i < N; ++i)
cout << v[i] << ' ';
cout << endl;
}
int main() {
MyVector
init(v1);
print(v1);
MyVector
init(v2);
print(v2);
MyVector
v3 = v1 + v2;
print(v3);
// Now supported:
MyVector
v4 = v1 + v2 + v3;
print(v4);
MyVector
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]
Template compilation models
You have certainly noticed by now that all our template examples place fully-defined templates within each compilation unit. (For example, we place them completely within single-file programs or in header files for multi-file programs.) This runs counter to the conventional practice of separating ordinary function definitions from their declarations by placing the latter in header files and the function implementations in separate (that is, .cpp) files. Everyone knows the reason for this separation: non-inline function bodies in header files can lead to multiple function definitions, which results in a linker error. A nice side benefit of this approach is that vendors can distribute pre-compiled code along with headers so that users cannot see their function implementations, and compile times are shorter since header files are smaller.
The inclusion model
Templates, on the other hand, are not code, per se, but instructions for code generation; only template instantiations are real code. When a compiler has seen a complete template definition during a compilation and then encounters a point of instantiation for that template in the same translation unit, it must deal with the fact that an equivalent point of instantiation may be present in another translation unit. The most common approach consists in generating the code for the instantiation in every translation unit and let the linker weed out duplicates. That particular approach also works well with inline functions that cannot be inlined and with virtual function tables, which is one of the reasons for its popularity. Nonetheless, several compilers prefer instead to rely on more complex schemes to avoid generating a particular instantiation more than once. Either way, it is the responsibility of the C++ translation system to avoid errors due to multiple equivalent points of instantiation.