1
3.1
You can also manually instantiate classes and static data members. When explicitly instantiating a class, all member functions for the requested specialization are instantiated, except any that may have been explicitly instantiated previously. Using only implicit instantiation has the advantage here: only member functions that actually get called are instantiated. Explicit instantiation is intended for large projects in which a hefty chunk of compilation time can be avoided. Whether you use implicit or explicit instantiation is independent of which template compilation you use, of course; you can use manual instantiation with either the inclusion model or the separation model (discussed in the next section).
The separation model
The separation model of template compilation allows you to separate function template definitions or static data member definitions from their declarations across translation units, just like you do with ordinary functions and data, by exporting templates. After reading the preceding two sections, this must sound strange indeed. Why bother to have the inclusion model in the first place if you can just adhere to the status quo? The reasons are both historical and technical.
Historically, the inclusion model was the first to experience widespread commercial use. Part of the reason for that was that the separation model was not well specified until late in the standardization process. It turns out that the inclusion model is the easier of the two to implement. All C++ compilers support the inclusion model. A lot of working code was in existence long before the semantics of the separation model were finalized.
The technical aspect reflects the fact that the separation model is difficult to implement. In fact, as of summer 2003 only one compiler front end (EDG) supports the separation model, and at the moment it still requires that template source code be available at compile time to perform instantiation on demand. Plans are in place to use some form of intermediate code instead of requiring that the original source be at hand, at which point you will be able to ship "pre-compiled" templates without shipping source code. Because of the lookup complexities explained earlier in this chapter (about dependent names being looked up in the template definition context), a full template definition still has to be available in some form when you compile a program that instantiates it.
The syntax to separate the source code of a template definition from its declaration is easy enough. You use the export keyword:
// C05:OurMin2.h
// Declares min as an exported template
//! (Only works with EDG-based compilers)
#ifndef OURMIN2_H
#define OURMIN2_H
export template<typename T> const T& min(const T&,
const T&);
#endif
Similar to inline or virtual, the export keyword need only be mentioned once in a compilation stream, where an exported template is introduced. For this reason, we need not repeat it in the implementation file, but it is considered good practice to do so:.
// C05:OurMin2.cpp
// The definition of the exported min template
//! (Only works with EDG-based compilers)
#include "OurMin2.h"
export
template<typename T> const T& min(const T& a, const T& b) {
return (a < b) ? a : b;
} ///:~
The UseMin files used previously only need to be updated to include the correct header file (OurMin2.h), and the main program need not change at all. Although this appears to give true separation, the file with the template definition (OurMin2.cpp) must still be shipped to users (because it must be processed for each instantiation of min) until such time as some form of intermediate code representation of template definitions is supported. So while the standard does provide for a true separation model, not all of its benefits can be reaped today. Only one family of compilers currently support export (those based on the EDG front end), and these compilers currently do not exploit the potential ability to distribute template definitions in compiled form.
Summary
Templates have gone far beyond simple type parameterization! When you combine argument type deduction, custom specialization, and template metaprogramming, C++ templates emerge as a powerful code generation mechanism.
One of the weaknesses of C++ templates we skipped in this chapter is the difficulty in interpreting compile-time error messages. When you’re not used to it, the quantity of inscrutable text spewed out by the compiler is quite overwhelming. If it’s any consolation, C++ compilers have actually gotten a lot better about this. Leor Zolman has written a nifty tool STLFilt, that renders these error messages much more readable by extracting the useful information and throwing away the rest.[78]
Another important idea to take away from this chapter is that a template implies an interface. That is, even though the template keyword says "I’ll take any type," the code in a template definition actually requires that certain operators and member functions be supported—that’s the interface. So in reality, a template definition is saying, "I’ll take any type that supports this interface." Things would be much nicer if the compiler could simply say, "Hey, this type that you’re trying to instantiate the template with doesn’t support that interface—can’t do it." Using templates, therefore, constitutes a sort of "latent type checking" that is more flexible than the pure object-oriented practice of requiring all types to derive from certain base classes.
In Chapters 6 and 7 we explore in depth the most famous application of templates, the subset of the standard C++ library commonly known as the Standard Template Library (STL). Chapters 9 and 10 also use template techniques not found in this chapter.
Exercises
1. Write a unary function template that takes a single type template parameter. Create a full specialization for the type int. Also create a non-template overload for this function that takes a single int parameter. Have your main program invoke three function variations.
2. Write a class template that uses a vector to implement a stack data structure.
38. Modify your solution to the previous exercise so that the type of the container used to implement the stack is a template template parameter.
39. In the following code, the class NonComparable does not have an operator=( ). Why would the presence of the class HardLogic cause a compile error, but SoftLogic would not?
class Noncomparable {};
struct HardLogic {
Noncomparable nc1, nc2;
void compare() {
return nc1 == nc2; // Compiler error
}
};
template<class T>
struct SoftLogic {
Noncomparable nc1, nc2;
void noOp() {}
void compare() {
nc1 == nc2;