18.2.3. Classes, Namespaces, and Scope

Name lookup for names used inside a namespace follows the normal lookup rules: The search looks outward through the enclosing scopes. An enclosing scope might be one or more nested namespaces, ending in the all-encompassing global namespace. Only names that have been declared before the point of use that are in blocks that are still open are considered:

namespace A {
    int i;
    namespace B {
        int i;         // hides A::i within B
        int j;
        int f1()
        {
            int j;    // j is local to f1 and hides A::B::j
            return i; // returns B::i
        }
    }  // namespace B is closed and names in it are no longer visible
    int f2() {
       return j;      // error: j is not defined
    }
    int j = i;        // initialized from A::i
}

When a class is wrapped in a namespace, the normal lookup still happens: When a name is used by a member function, look for that name in the member first, then within the class (including base classes), then look in the enclosing scopes, one or more of which might be a namespace:

namespace A {
    int i;
    int k;

    class C1 {
    public:
        C1(): i(0), j(0) { }   // ok: initializes C1::i and C1::j
        int f1() { return k; } // returns A::k
        int f2() { return h; } // error: h is not defined
        int f3();
    private:
        int i;                 // hides A::i within C1
        int j;
    };
    int h = i;                 // initialized from A::i
}
// member f3 is defined outside class C1 and outside namespace A
int A::C1::f3() { return h; }  // ok: returns A::h

With the exception of member function definitions that appear inside the class body (§ 7.4.1, p. 283), scopes are always searched upward; names must be declared before they can be used. Hence, the return in f2 will not compile. It attempts to reference the name h from namespace A, but h has not yet been defined. Had that name been defined in A before the definition of C1, the use of h would be legal. Similarly, the use of h inside f3 is okay, because f3 is defined after A::h.


Image Tip

The order in which scopes are examined to find a name can be inferred from the qualified name of a function. The qualified name indicates, in reverse order, the scopes that are searched.


The qualifiers A::C1::f3 indicate the reverse order in which the class scopes and namespace scopes are to be searched. The first scope searched is that of the function f3. Then the class scope of its enclosing class C1 is searched. The scope of the namespace A is searched last before the scope containing the definition of f3 is examined.

Argument-Dependent Lookup and Parameters of Class Type
Image

Consider the following simple program:

std::string s;
std::cin >> s;

As we know, this call is equivalent to (§ 14.1, p. 553):

operator>>(std::cin, s);

This operator>> function is defined by the string library, which in turn is defined in the std namespace. Yet we can we call operator>> without an std:: qualifier and without a using declaration.

We can directly access the output operator because there is an important exception to the rule that names defined in a namespace are hidden. When we pass an object of a class type to a function, the compiler searches the namespace in which the argument’s class is defined in addition to the normal scope lookup. This exception also applies for calls that pass pointers or references to a class type.

In this example, when the compiler sees the “call” to operator>>, it looks for a matching function in the current scope, including the scopes enclosing the output statement. In addition, because the >> expression has parameters of class type, the compiler also looks in the namespace(s) in which the types of cin and s are defined. Thus, for this call, the compiler looks in the std namespace, which defines the istream and string types. When it searches std, the compiler finds the string output operator function.

This exception in the lookup rules allows nonmember functions that are conceptually part of the interface to a class to be used without requiring a separate using declaration. In the absence of this exception to the lookup rules, either we would have to provide an appropriate using declaration for the output operator:

using std::operator>>;         // needed to allow cin >> s

or we would have to use the function-call notation in order to include the namespace qualifer:

std::operator>>(std::cin, s); // ok: explicitly use std::>>

There would be no way to use operator syntax. Either of these declarations is awkward and would make simple uses of the IO library more complicated.

Lookup and std::move and std::forward

Many, perhaps even most, C++ programmers never have to think about argument-dependent lookup. Ordinarily, if an application defines a name that is also defined in the library, one of two things is true: Either normal overloading determines (correctly) whether a particular call is intended for the application version or the one from the library, or the application never intends to use the library function.

Now consider the library move and forward functions. Both of these functions are template functions, and the library defines versions of them that have a single rvalue reference function parameter. As we’ve seen, in a function template, an rvalue reference parameter can match any type (§ 16.2.6, p. 690). If our application defines a function named move that takes a single parameter, then—no matter what type the parameter has—the application’s version of move will collide with the library version. Similarly for forward.

As a result, name collisions with move (and forward) are more likely than collisions with other library functions. In addition, because move and forward do very specialized type manipulations, the chances that an application specifically wants to override the behavior of these functions are pretty small.

The fact that collisions are more likely—and are less likely to be intentional—explains why we suggest always using the fully qualified versions of these names (§ 12.1.5, p. 470). So long as we write std::move rather than move, we know that we will get the version from the standard library.

Friend Declarations and Argument-Dependent Lookup
Image

Recall that when a class declares a friend, the friend declaration does not make the friend visible (§ 7.2.1, p. 270). However, an otherwise undeclared class or function that is first named in a friend declaration is assumed to be a member of the closest enclosing namespace. The combination of this rule and argument-dependent lookup can lead to surprises:

namespace A {
    class C {
        // two friends; neither is declared apart from a friend declaration
        // these functions implicitly are members of namespace A
        friend void f2();     // won't be found, unless otherwise declared
        friend void f(const C&); // found by argument-dependent lookup
    };
}

Here, both f and f2 are members of namespace A. Through argument-dependent lookup, we can call f even if there is no additional declaration for f:

int main()
{
    A::C cobj;
    f(cobj);   // ok: finds A::f through the friend declaration in A::C
    f2();      // error: A::f2 not declared
}

Because f takes an argument of a class type, and f is implicitly declared in the same namespace as C, f is found when called. Because f2 has no parameter, it will not be found.


Exercises Section 18.2.3

Exercise 18.18: Given the following typical definition of swap § 13.3 (p. 517), determine which version of swap is used if mem1 is a string. What if mem1 is an int? Explain how name lookup works in both cases.

void swap(T v1, T v2)
{
    using std::swap;
    swap(v1.mem1, v2.mem1);
    // swap remaining members of type T
}

Exercise 18.19: What if the call to swap was std::swap(v1.mem1, v2.mem1)?


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.255.178