DekGenius.com
Team LiB   Previous Section   Next Section

2.3 Name Lookup

When the compiler reads an identifier, it must look up the identifier to determine which declaration it comes from. In most cases, you can readily tell which identifier is which, but it is not always so simple. A small mistake can sometimes lead to code that compiles successfully but runs incorrectly because an identifier refers to a different object from the one you intended. To understand name lookup fully, you must first understand namespaces (covered later in this chapter), functions (Chapter 5), classes (Chapter 6), and templates (Chapter 7).

Name lookup takes place before overloaded functions are resolved and before the access level of class members is checked. If a name is found in an inner scope, the compiler uses that declaration, even if a better declaration would be found in an outer scope. Example 2-3 shows how problems can arise when an overloaded function is declared in more than one namespace. The function func(int) is global, and func(double) is defined in namespace N. Inside call_func, the compiler looks up the name func by searching first in the local scope (that is, the function body), then in namespace N, where it finds func(double). Name lookup stops at that point because the compiler found a match. Therefore, func(3) converts 3 to type double and calls func(double). The main function brings all the overloaded func functions into its scope (with using declarations, which are described at the end of this chapter), so name lookup can find the best match, which is func(int).

Example 2-3. Name lookup trumps overload resolution
void func(int i)
{
  std::cout << "int: " << i << '\n';
}

namespace N {
  void func(double d)
  {
    std::cout << "double: " << std::showpoint << d << '\n';
  }

  void call_func(  )
  {
    // Even though func(int) is a better match, the compiler finds
    // N::func(double) first.
    func(3);
  }
}

int main(  )
{
  N::call_func(  );       // Prints "double: 3.000000"
  using N::func;
  using ::func;
  // Now all overloaded func(  )s are at the same scope level.
  func(4);              // Prints "int: 4"
}

Refer to Chapter 5 for more information about overloaded functions and to Chapter 6 for information about access levels in a class declaration.

2.3.1 Qualified Name Lookup

To specify a particular namespace for looking up a name, qualify the name using the scope operator (::). A name that follows the global scope operator (the unary ::) is looked up in the global scope (outside of all namespaces). The name must be declared in the global scope, not in a nested scope, or the name must have been introduced into the global scope by a using directive or using declaration (see Section 2.7 later in this chapter).

Use the global scope operator to access names that have been hidden by an inner scope. Example 2-4 shows this use of the :: operator to access the global x from within main after an inner x has been declared.

Example 2-4. The global scope operator
#include <iostream>
#include <ostream>

int x = 42;

int main(  )
{
  double x = 3.1415;         // Hides the global x
  std::cout << x << '\n';    // Prints 3.1415
  std::cout << ::x << '\n';  // Prints 42
}

The binary scope resolution operator (also ::) requires a namespace or class name as its lefthand operand, and an identifier as its righthand operand. The identifier is looked up in the scope of the lefthand namespace or class. Example 2-5 shows the scope resolution operator untangling a mess made by using the same names for different kinds of entities. Notice how the inner counter hides the outer counter, so the simple name counter refers to the int variable. The lefthand operand to ::, however, must be a class or namespace, so in the expression counter::c, the inner counter does not hide the outer counter.

Example 2-5. The scope resolution operator
#include <iostream>
#include <ostream>

namespace n {
  struct counter {
    static int n;
  };
  double n = 2.71828;
}

int n::counter::n = 42; // Defines static data member

int main(  )
{
  int counter = 0;    // Unrelated to n::counter
  int n = 10;         // Hides namespace n
  ::n::counter x;     // Refers to namespace n

  std::cout << n::counter::n; // Prints 42
  std::cout << n::n;          // Prints 2.71828
  std::cout << x.n;           // Prints 42
  std::cout << n;             // Prints 10
  std::cout << counter;       // Prints 0
}

2.3.2 Unqualified Name Lookup

The compiler looks up an unqualified name, that is, a bare identifier or operator symbol, in a series of scopes in order to find its declaration. The simple rule is that the innermost scope is searched first, and succeeding outer scopes are searched until a matching declaration is found. Additional named scopes are searched, depending on the context of the usage, as explained in this section and the next (Section 2.3.3). An associated simple rule is that a name must be declared before it is used, reading the source file from top to bottom.

In a class definition, the class is searched first; the declaration must appear before it is used, reading the class definition from top to bottom. The immediate base classes are searched next (in declaration order), and their base classes are searched. If the class is nested in another class, the containing class is searched, and its base classes are searched. If the class is local to a function, the block that contains the class is searched, then enclosing blocks are searched. Finally, the namespaces that contain the class declaration are searched. That is, the namespace that immediately contains the class is searched first; if that namespace is nested within another namespace, the outer namespace is searched next, and so on. If the class is nested, the namespaces that contain the outer class or classes are searched.

In the body of a member function, names are looked up first in the local scope and nested scopes of the function body. Then the class is searched; the name can be declared anywhere in the class definition, even if that declaration appears after the member function definition. (This is an exception to the "declare-before-use" rule.) Such names can be used in a parameter type, a default argument value, or the function body, but not in the function's return type. The name is then looked up in the manner described earlier for other names used in a class definition.

The name lookup rules permit a member function to use a name that is declared later in the class definition, but the name lookup rules do not trump the syntax and parsing rules. In particular, the parser must be able to distinguish between names used as types from other names. In the following example, the name big is used as a type, and is declared as a type in the class definition, but when the compiler first sees the name big in the member function declaration, the only big in scope is the global object, so the compiler parses the function declaration incorrectly:

int big;
// typedef big float;
struct demo {
  void func(big); // Error
  typedef big long;
};

If the declaration int big were a typedef instead, the declaration of func would be parsed as intended, and name lookup would find the nested type demo::big for the parameter type of func. A simpler solution is to move the typedef big long; to the start of the class definition, which is the style used throughout this book.

If a class or namespace contains a using directive (described later in this chapter), the used class or namespace is also searched.

A friend declaration does not add its name to the class scope. Thus, the rules for name lookup are slightly different than they are for a member function. If a friend function is defined within the body of the class granting friendship, the name is looked up in the class scope, just as it would be for a member function. If the function is defined outside the class, the class is not searched, so the rules are the same as for an ordinary function. Example 2-6 shows how the two different lookup rules can cause confusion.

Example 2-6. Looking up names in a friend function
class foo {
public:
  friend void bar1(foo& f) {
    ++y;          // OK: refers to foo::y
  }
  friend void bar2(foo& f);
private:
  static int y;
  int x;
};

void bar2(foo& f) {
  ++y;           // Error: y not in scope
}

If the friend is a member function, the function and other names in its declaration are looked up first in the class granting friendship, and, if not found, in the class that contains the friend declaration. (See Example 2-7.)

Example 2-7. Declaring friend member functions
class test {
public:
  typedef int T1;
  typedef float T2;
  void f(T1);
  void f(T2);
};
class other {
  typedef char T2;
  friend void test::f(T1); // Look up f and T1 in test.
  friend void test::f(T2); // Look up f and T2 in test before looking it up in
                           // other.
};

In the definition of a class member (function or static data) outside of the class declaration, the lookup searches the class and ancestor classes, but only after the class name appears in the declarator. Thus, the type specifier (a function's return type or the type of a static data member) is not looked up in the class declaration unless the type name is explicitly qualified. (Declarators and type specifiers are covered later in this chapter.) Example 2-8 shows the consequences of this rule.

Example 2-8. Defining members outside of a class declaration
class node {
public:
  enum color { red, black };
  node(color x);
  color toggle(color c);
private:
  color c;
  static color root_color;
};

// Must qualify node::color and node::root_color, but initializer is in the scope 
// of node, so it doesn't need to be qualified

node::color node::root_color = red;

// Similarly, return type must be qualified, but parameter type does not need to
// be.
node::color node::toggle(color c)
{
  return static_cast<color>(1 - c);
}

In a template declaration, the lookup rules for unqualified names have an additional wrinkle that depends on the template parameters and arguments. See Chapter 7 for details.

2.3.3 Argument-Dependent Name Lookup

Argument-dependent name lookup is an additional rule for looking up unqualified function names. The rule specifies additional classes and namespaces to search based on the types of the function arguments. Argument-dependent name lookup is also known as Koenig lookup, named after Andrew Koenig, the creator of this lookup rule. The short version of the rule is that the compiler looks up a function name in the usual places, and in the namespaces that contain the user-defined types (classes and enumerations) of the function arguments.

The slightly longer version of the Koenig lookup rule is that the compiler first searches all the usual places, as described earlier in this chapter. If it does not find a declaration, it then searches an additional list of classes and namespaces. The additional list depends on the types used for all of the function's argument types:

  • For a class type, the compiler searches the class and its namespaces, plus all ancestor classes and their namespaces.

  • For a pointer to a data member, the compiler searches its class and its namespaces, plus all ancestor classes and their namespaces.

  • For a function pointer or reference, the compiler searches the classes and namespaces associated with the return type and all parameter types. For a pointer or reference to a member function, the compiler also searches its class and all ancestor classes, and their namespaces.

  • For a union or enumerated type, the namespace that contains the declaration is searched. If the type is a class member, its class is searched.

Example 2-9 shows a typical case in which argument-dependent name lookup is needed. The operator<< function is declared in the std namespace in the <string> header. It is not a member function of ostream, and the only way the compiler can find the operator is to search the std namespace. The fact that its arguments are in the std namespace tells the compiler to look there.

Example 2-9. Argument-dependent name lookup
#include <string>
#include <iostream>
#include <ostream>
int main(  )
{
  std::string message("Howdy!\n");
  std::cout << message;
}

Another way to look at argument-dependent lookup is to consider a simple class declaration. For example, with rational numbers, to support the customary and usual arithmetic operators, you might choose to declare them as member functions:

namespace numeric {
  class rational {
    ...
    rational operator+(const rational& other);
    rational operator+(int i);
  };
}

Expressions such as r1 + 42 compile successfully because they are equivalent to member function calls such as r1.operator+(42), so the + operator is found as a member of rational (assuming r1 is an instance of rational). But 42 + r1 does not work because an integer cannot have a member function—e.g., 42.operator+(r1). Instead, you must declare an operator at the namespace level:

namespace numeric {
  class rational { ... };
  rational operator+(int i, const rational& r);
  ...
}

In order to compile the expression 42 + r1, the compiler needs to find operator+ in the numeric namespace. Without a using directive, the compiler has no way of knowing it needs to search the numeric namespace, but the compiler knows that the type of the second argument is declared in numeric. Thus, argument-dependent lookup allows for the everyday, expected use of overloaded operators.

    Team LiB   Previous Section   Next Section