#include <iostream>
#include <string>
#include "Nobloat.h"
using namespace std;
template<class StackType>
void emptyTheStack(StackType& stk) {
while (stk.size() > 0) {
cout << stk.top() << endl;
stk.pop();
}
}
// An overload for emptyTheStack (not a specialization!)
template<class T>
void emptyTheStack(Stack<T*>& stk) {
while (stk.size() > 0) {
cout << *stk.top() << endl;
stk.pop();
}
}
int main() {
Stack<int> s1;
s1.push(1);
s1.push(2);
emptyTheStack(s1);
Stack<int *> s2;
int i = 3;
int j = 4;
s2.push(&i);
s2.push(&j);
emptyTheStack(s2);
} ///:~
For convenience we have included two emptyStack function templates. Since function templates don’t support partial specialization, we provide overloaded templates. The second version of emptyStack is more specialized than the first, so it is chosen whenever pointer types are used. Three class templates are instantiated in this program: Stack<int>, Stack<void*>, and Stack<int*>. Stack<void*> is implicitly instantiated because Stack<int*> derives from it. If a program uses instantiations for many pointer types, the savings in code size over just using a single Stack template can be substantial.
Name lookup issues
When the compiler encounters an identifier it must determine the type and scope (and in the case of variables, the lifetime) of the entity the identifier represents. This is common knowledge among software developers, but the plot thickens when templates are involved. Because not everything is known about a template when its definition is first seen by the compiler, the compiler must hold off until the template is instantiated before it can determine whether it is being used properly. This predicament leads to a two-phase process for template compilation.
Names in templates
In the first phase the compiler parses the template definition looking for obvious syntax errors and resolving all the names it can. The names it can resolve during parsing are those that do not depend on template parameters, which the compiler takes care of through normal name lookup means (and also through argument-dependent lookup, discussed below, if necessary). The names it can’t resolve are the so-called dependent names, which are names that in some way depend on template parameters. These can’t be resolved until the template is instantiated with its actual arguments. Instantiation, therefore, is the second phase of template compilation. During this second phase, the compiler determines whether an explicit specialization of the template in question needs to be used instead of the primary template.
Before you see an example, two more terms need to be defined. A qualified name is a name with a class-name prefix, a name with an object name and a dot operator, or a name with a pointer to an object and an arrow operator. Examples of qualified names are found in the following expressions:.
MyClass::f();
x.f();
p->f();
We have used qualified names many times in this book, and most recently in connection with the typename keyword. These are called qualified names because the target names (like f above) are explicitly associated with a class, which tells the compiler where to look for the declarations of those names.
The other term to discuss is argument-dependent lookup[60] (ADL), which is a technique originally designed to simplify using non-member function calls (including operators) declared in namespaces. Consider the following simple code excerpt:.
#include <iostream>
#include <string>
//…
std::string s("hello");
std::cout << s << std::endl;
Note that there is no using namespace std; directive, which is the typical practice inside header files, for example. Without such a directive, it is necessary to use the std:: qualifier on the items that are in the std namespace. We have, however, not qualified everything from std that we are using. Can you see what we have left unqualified?.
We have not specified which operator functions to use. We want the following to happen, but we don’t want to have to type it!
std::operator<<(std::operator<<(std::cout,s),std::endl);
To make the original output statement work as desired, ADL specifies that when an unqualified function call appears and its declaration is not in (normal) scope, the namespaces (or class scopes) of each of its arguments are searched for a matching function declaration. In the original statement, the first function call is:.
operator<<(std::cout, s);
Since there is no such function in scope in our original excerpt, the compiler notes that this function’s first argument (std::cout) is in the namespace std; so it adds that namespace to the list of scopes to search for a unique function that best matches the signature operator<<(std::ostream&, std::string). It finds this function declared in the std namespace via the <string> header, so that is the function that is called. Namespaces would be very inconvenient without ADL. (But note that, in general, ADL brings in all declarations of the name in question from all eligible namespaces—if there is no best match, an ambiguity will result.) To turn off ADL, you can enclose the function name in parentheses:.
(f)(x, y); // ADL suppressed
Now consider the following program, from a presentation by Herb Sutter:
// Lookup.cpp
// Only works on EDG and Metrowerks (special option)
#include <iostream>
using std::cout;
void f(double) { cout << "f(double)\n"; }
template<class T>
class X {
public:
void g() { f(1); }
};
void f(int) { cout << "f(int)\n"; }
int main() {
X<int>().g();
}
The only compiler we have that gets this correct right out of the box is the Edison Design Group front end. (A number of compilers use this front end, including Comeau C++.) The output should be:.
f(double)
because f is a non-dependent name that can be resolved early by looking in the context where the template is defined, when only f(double) is in scope. Unfortunately, there is a lot of code in existence that depends on the non-standard behavior of binding the call to f(1) inside g( ) to the latter f(int), so compiler writers have been reluctant to make the change. (Some compilers, such as the Metrowerks compiler, have an option to enable the correct lookup behavior.).