Выбрать главу

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 <iostream>

#include <typeinfo>

using std::cout;

using std::endl;

void g() { cout << "global g()\n"; }

template <class T>

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<class T>

void swap(T& t1, T& t2) {

  cout << "global swap\n";

  T temp = t1;

  t1 = t2;

  t2 = temp;

}

template<class T>

class X : public Y<T> {

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<int> x;

  cout << x.f() << endl;

} ///:~

The output from this program should be:

global g()

Y<int>::h()

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 dependent base class, so it can’t be searched at template definition time).

·         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<T> by inheritance. There is no function h( ) inside of X, so lookup will naturally want to search the scope of X’s base class, Y<T>. Since this is a dependent name, it is looked up at instantiation time, when Y<T> can be reliably known (including any potential specializations that might have been written after the definition of X); so it calls Y<int>::h( ).

·         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 not dependent, because std is a fixed namespace; so the compiler knows to look there for the proper declaration. (The qualifier on the left of the "::" must mention a template parameter for a qualified name to be considered dependent.) The std::swap( ) function template later generates std::swap(int&, int&), at instantiation time. No more dependent names remain in X<T>::f( ).

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

A friend function declaration inside a class allows a non-member function to access non-public members of that class. If the friend function name is qualified, it will of course be found in the namespace or class that qualifies it. If it is unqualified, however, the compiler must make an assumption about where the definition of the friend function will be, since all identifiers must have a unique scope. The expectation is that the function will be defined in the nearest enclosing namespace (non-class) scope that contains the class granting friendship. Often this is just the global scope. The following non-template example clarifies this issue.

//: C05:FriendScope.cpp

#include <iostream>

using namespace std;

class Friendly {

  int i;

public:

  Friendly(int theInt) { i = theInt; }

  friend void f(const Friendly&); // needs global def.

  void g() { f(*this); }

};

void h() {

  f(Friendly(1));  // uses ADL

}

void f(const Friendly& fo) {  // definition of friend

  cout << fo.i << endl;

}

int main() {

  h();// prints 1

  Friendly(2).g();   // prints 2

} ///:~

The declaration of f( ) inside the Friendly class is unqualified, so the compiler will expect to be able to eventually link that declaration to a definition at file scope (the namespace scope that contains Friendly in this case). That definition appears after the definition of the function h( ). The linking of the call to f( ) inside h( ) to the same function is a separate matter, however. This is resolved by ADL. Since the argument of f( ) inside h( ) is a Friendly object, the Friendly class is searched for a declaration of f( ), which succeeds. If the call were f(1) instead (which makes some sense since 1 can be implicitly converted to Friendly(1)), the call should fail, since there is no hint of where the compiler should look for the declaration of f( ). The EDG compiler correctly complains that f is undefined in that case.