void f(int) { cout << "f(int)\n"; }
int main() {
X
}
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.).
Here is a more detailed example, also based on an example from Herb Sutter:
//: C05:Lookup2.cpp
//{-bor}
//{-g++}
// Microsoft: use option –Za (ANSI mode)
#include
#include
using std::cout;
using std::endl;
void g() { cout << "global g()\n"; }
template
class Y {
public:
void g() { cout << "Y<" << typeid(T).name()
<< ">::g()\n"; }
void h() { cout << "Y<" << typeid(T).name()
<< ">::h()\n"; }
typedef int E;
};
typedef double E;
template
void swap(T& t1, T& t2) {
cout << "global swap\n";
T temp = t1;
t1 = t2;
t2 = temp;
}
template
class X : public Y
public:
E f() {
g();
this->h();
T t1 = T(), t2 = T(1);
cout << t1 << endl;
swap(t1, t2);
std::swap(t1, t2);
cout << typeid(E).name() << endl;
return E(t2);
}
};
int main() {
X
cout << x.f() << endl;
} ///:~
The output from this program should be:
global g()
Y
0
global swap
double
1
Looking at the declarations inside of X::f( ), we observe the following:
· The return type of X::f( ), which is E, is not a dependent name, so it is looked up when the template is parsed, and the typedef naming E as a double is found. This may seem strange, since with non-template classes the declaration of E in the base class would be found first, but those are the rules. (The base class, Y, is a
· The call to g( ) is also non-dependent, since there is no mention of T. If g had parameters that were of class type of defined in another namespace, ADL would take over, since there is no g with parameters in scope. As it is, this call matches the global declaration of g( ).
· The call this->h( ) is a qualified name, and the object that qualifies it (this) refers to the current object, which is of type X, which in turn depends on the name Y
· The declarations of t1 and t2 are dependent, of course.
· The call to operator<<(cout, t1) is dependent, since t1 is of type T. This is looked up later when T is int, and the inserter for int is found in std.
· The unqualified call to swap( ) is dependent because its arguments are of type T. This ultimately causes a global swap(int&, int&) to be instantiated, of course.
· The qualified call to std::swap( ) is
To clarify and summarize: name lookup is done at the point of instantiation if the name is dependent, except that for unqualified dependent names the normal name lookup is also attempted early, at the point of definition. All non-dependent names in templates are looked up early, at the time the template definition is parsed. (If necessary, another lookup occurs at instantiation time, when the type of the actual argument is known.).
(Whew!) If you have studied this example to the point that you understand it, prepare yourself for yet another surprise in the next section when friend declarations enter the picture.
Templates and friends