9.2 Looking Up Names

Ru-Brd

9.2 Looking Up Names

There are many small details to looking up names in C++, but we will focus only on a few major concepts. The details are necessary to ensure only that (1) normal cases are treated intuitively, and (2) pathological cases are covered in some way by the standard.

Qualified names are looked up in the scope implied by the qualifying construct. If that scope is a class, then base classes may also be looked up. However, enclosing scopes are not considered when looking up qualified names. The following illustrates this basic principle:

 int x;  class B {    public:      int i;  };  class D : public B {  };  void f(D* pd)  {      pd->i = 3;  // finds  B::i      D::x = 2;  // ERROR: does not find  ::x  in the enclosing scope  } 

In contrast, unqualified names are typically looked up in successively more enclosing scopes (although in member function definitions the scope of the class and its base classes is searched before any other enclosing scopes). This is called ordinary lookup . Here is a basic example showing the main idea underlying ordinary lookup:

 extern int count;  // (1)  int lookup_example(int count)  // (2)  {      if (count < 0) {          int count = 1;  // (3)  lookup_example(count);  // unqualified  count  refers to (3)  }      return count + ::count;  // the first (unqualified)  count  refers to (2);  }  // the second (qualified)  count  refers to (1)  

A more recent twist to the lookup of unqualified names is that ”in addition to ordinary lookup ”they may sometimes undergo so-called argument-dependent lookup ( ADL ). [1] Before proceeding with the details of ADL, let's motivate the mechanism with our perennial max() template:

[1] This is also called Koenig lookup (or extended Koenig lookup ) after Andrew Koenig, who first proposed a variation of this mechanism.

 template <typename T>  inline T const& max (T const& a, T const& b)  {      return a < b ? b : a;  } 

Suppose now that we need to apply this template to a type defined in another namespace:

 namespace BigMath {      class BigNumber {   };      bool operator < (BigNumber const&, BigNumber const&);   }  using BigMath::BigNumber;  void g (BigNumber const& a, BigNumber const& b)  {   BigNumber x = max(a,b);   } 

The problem here is that the max() template is unaware of the BigMath namespace, but ordinary lookup would not find the operator < applicable to values of type BigNumber . Without some special rules, this greatly reduces the applicability of templates in the context of C++ namespaces. ADL is the C++ answer to those "special rules."

9.2.1 Argument-Dependent Lookup

ADL applies only to unqualified names that look like they name a nonmember function in a function call. If ordinary lookup finds the name of a member function or the name of a type, then ADL does not happen. ADL is also inhibited if the name of the function to be called is enclosed in parentheses.

Otherwise, if the name is followed by a list of argument expressions enclosed in parentheses, ADL proceeds by looking up the name in namespaces and classes "associated with" the types of the call arguments. The precise definition of these associated namespaces and associated classes is given later, but intuitively they can be thought as being all the namespaces and classes that are fairly directly connected to a given type. For example, if the type is a pointer to a class X , then the associated classes and namespace would include X as well as any namespaces or classes to which X belongs.

The precise definition of the set of associated namespaces and associated classes for a given type is determined by the following rules:

  • For built-in types, this is the empty set.

  • For pointer and array types, the set of associated namespaces and classes is that of the underlying type.

  • For enumeration types, the associated namespace is the namespace in which the enumeration is declared. For class members , the enclosing class is the associated class.

  • For class types (including union types) the set of associated classes is the type itself, the enclosing class, and any direct and indirect base classes. The set of associated namespaces is the namespaces in which the associated classes are declared. If the class is a class template instantiation, then the types of the template type arguments and the classes and namespaces in which the template template arguments are declared are also included.

  • For function types, the sets of associated namespaces and classes comprise the namespaces and classes associated with all the parameter types and those associated with the return type.

  • For pointer-to-member-of-class- X types, the sets of associated namespaces and classes include those associated with X in addition to those associated with the type of the member. (If it is a pointer-to- member-function type, then the parameter and return types can contribute too.)

ADL then looks up the name in all the associated namespaces as if the name had been qualified with each of these namespaces in turn , except that using-directives are ignored. The following example illustrates this:

  // details/adl.cpp  #include <iostream>  namespace X {      template<typename T> void f(T);  }  namespace N {      using namespace X;      enumE{e1};      void f(E) {          std::cout << "N::f(N::E) called\n";      }  }  void f(int)  {      std::cout << "::f(int) called\n";  }  int main()  {      ::f(N::e1);  // qualified function name: no ADL  f(N::e1);  // ordinary lookup finds  ::f()  and ADL finds  N::f()  ,  }  // the latter is preferred  

Note that in this example, the using-directive in namespace N is ignored when ADL is performed. Hence X::f() is never even a candidate for the call in main() .

9.2.2 Friend Name Injection

A friend function declaration can be the first declaration of the nominated function. If this is the case, then the function is assumed to be declared in the nearest namespace scope (or perhaps the global scope) enclosing the class containing the friend declaration. A relatively controversial issue is whether that declaration should be visible in the scope in which it is "injected." It is mostly a problem with templates. Consider the following example:

 template<typename T>  class C {   friend void f();      friend void f(C<T> const&);   };  void g (C<int>* p)  {      f();  // Is  f()  visible here?  f(*p);  // Is  f(C<int> const&)  visible here?  } 

The trouble is that if friend declarations are visible in the enclosing namespace, then instantiating a class template may make visible the declaration of ordinary functions. Some programmers find this surprising, and the C++ standard therefore specifies that friend declarations do not ordinarily make the name visible in the enclosing scope.

However, there is an interesting programming technique that depends on declaring (and defining) a function in a friend declaration only (see Section 11.7 on page 174). Therefore the standard also specifies that friend functions are found when the class of which they are a friend is among the associated classes considered by ADL.

Reconsider our last example. The call f() has no associated classes or namespaces because there are no arguments: It is an invalid call in our example. However, the call f(*p) does have the associated class C<int> (because this is the type of *p ), and the global namespace is also associated (because this is the namespace in which the type of *p is declared). Therefore the second friend function declaration could be found provided the class C<int> was actually fully instantiated prior to the call. To ensure this, it is assumed that a call involving a lookup for friends in associated classes actually causes the class to be instantiated (if not done already). [2]

[2] Although this was clearly intended by those who wrote the C++ standard, it is not clearly spelled out in the standard.

9.2.3 Injected Class Names

The name of a class is "injected" inside the scope of that class itself and is therefore accessible as an unqualified name in that scope. (However, it is not accessible as a qualified name because this is the notation used to denote the constructors.) For example:

  // details/inject.cpp  #include <iostream>  int C;  class C {    private:      int i[2];    public:      static int f() {          return sizeof(C);      }  };  int f()  {      return sizeof(C);  }  int main()  {     std::cout << "C::f() = " <<C::f() << ","               << " ::f() = " <<::f() << std::endl;  } 

The member function C::f() returns the size of type C whereas the function ::f() returns the size of the variable C (in other words, the size of an int object).

Class templates also have injected class names. However, they're stranger than ordinary injected class names: They can be followed by template arguments (in which case they are injected class template names), but if they are not followed by template arguments they represent the class with its parameters as its arguments (or, for a partial specialization, its specialization arguments). This explains the following situation:

 template<template<typename> class TT> class X {  };  template<typename T> class C {     Ca;  // OK: same as ''  C<T> a;  ''  C<void> b;  // OK  X<C> c;  // ERROR:  C  without a template argument list   //        does not denote a template  X<::C> d;  // ERROR:  <:  is an alternative token for  [     X< ::C> e;  // OK: the space between  <  and  ::  is required  } 

Note how the unqualified name refers to the injected name and is not considered the name of the template if it is not followed by a list of template arguments. To compensate, we can force the name of the template to be found by using the file scope qualifier :: . This works, but we must then be careful not to create a so-called digraph token <: , which is interpreted as a left bracket . Although relatively rare, such errors result in perplexing diagnostics.

Ru-Brd


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net