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.
A drawback of this approach is obviously that all template source code is visible to the client. If you want to know exactly how your standard library is implemented, all you have to do is inspect the headers in your installation. There is little opportunity for library vendors to hide their implementation strategies. Another noticeable disadvantage of the inclusion model is that header files are much, much larger than they would be if function bodies were compiled separately. This can increase compile times dramatically over traditional compilation models.
To help reduce the large headers required by the inclusion model, C++ offers two (non-exclusive) alternative code organization mechanisms: you can manually instantiate each specialization using explicit instantiation or you can use exported templates, which actually support a large degree of separate compilation.
Explicit instantiation
You can manually direct the compiler to instantiate any template specializations of your choice. When you use this technique, there must be one and only one such directive for each such specialization; otherwise you might get multiple definition errors, just as you would with ordinary, non-inline functions with identical signatures. To illustrate, we first (erroneously) separate the declaration of the min template from earlier in this chapter from its definition, following the normal pattern for ordinary, non-inline functions. The following example consists of five files:.
· OurMin.h: contains the declaration of the min function template.
· OurMin.cpp: contains the definition of the min function template.
· UseMin1.cpp: attempts to use an int-instantiation of min
· UseMin2.cpp: attempts to use a double-instantiation of min
· MinMain.cpp: calls usemin1( ) and usemin2( )
Here are the files:
//: C05:OurMin.h
#ifndef OURMIN_H
#define OURMIN_H
// The declaration of min
template<typename T> const T& min(const T&, const T&);
#endif ///:~
// OurMin.cpp
#include "OurMin.h"
// The definition of min
template<typename T> const T& min(const T& a, const T& b) {
return (a < b) ? a : b;
}
//: C05:UseMin1.cpp {O}
#include <iostream>
#include "OurMin.h"
void usemin1() {
std::cout << min(1,2) << std::endl;
} ///:~
//: C05:UseMin2.cpp {O}
#include <iostream>
#include "OurMin.h"
void usemin2() {
std::cout << min(3.1,4.2) << std::endl;
} ///:~
//: C05:MinMain.cpp
//{L} UseMin1 UseMin2 MinInstances
void usemin1();
void usemin2();
int main() {
usemin1();
usemin2();
} ///:~
When we attempt to build this program, the linker reports unresolved external references for min<int>( ) and min<double>( ). The reason is that when the compiler encounters the calls to specializations of min in UseMin1 and UseMin2, only the declaration of min is visible. Since the definition is not available, the compiler assumes it will come from some other translation unit, and the needed specializations are therefore not instantiated at that point, leaving the linker to eventually complain that it cannot find them.
To solve this problem, we will introduce a new file, MinInstances.cpp, that explicitly instantiates the needed specializations of min:
//: C05:MinInstances.cpp {O}
#include "OurMin.cpp"
// Explicit Instantiations for int and double
template const int& min<int>(const int&, const int&);
template const double& min<double>(const double&,
const double&);
///:~
To manually instantiate a particular template specialization, you precede the specialization’s declaration with the template keyword. That’s it! Note that we must include OurMin.cpp, not OurMin.h, here, because the compiler needs the template definition to perform the instantiation. This is the only place where we have to do this in this program,[77] however, since it gives us the unique instantiations of min that we need; the declarations alone suffice for the other files. Since we are including OurMin.cpp with the macro preprocessor, we add include guards:.
//: C05:OurMin.cpp {O}
#ifndef OURMIN_CPP
#define OURMIN_CPP
#include "OurMin.h"
template<typename T> const T& min(const T& a, const T& b) {
return (a < b) ? a : b;
}
#endif ///:~
Now when we compile all the files together into a complete program, the unique instances of min are found, and the program executes correctly, giving the output: