I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

Two of the three cases are (fairly) obvious, but the third requires a good knowledge of C++'s name lookup rules梚n particular, Koenig lookup.

Let's start simple.



namespace A 


{


  struct X;


  struct Y;


  void f( int );


  void g( X );


}


namespace B


{


  void f( int i )


  {


    f( i );   // which f()?


  }


This f() calls itself, with infinite recursion. The reason is that the only visible f() is B::f() itself.

There is another function with signature f(int), namely the one in namespace A. If B had written "using namespace A;" or "using A::f;", then A::f(int) would have been visible as a candidate when looking up f(int), and the f(i) call would have been ambiguous between A::f(int) and B::f(int). Since B did not bring A::f(int) into scope, however, only B::f(int) can be considered, so the call unambiguously resolves to B::f(int).

And now for a twist:



void g( A::X x ) 


{


  g( x );   // which g()?


}


This call is ambiguous between A::g(X) and B::g(X). The programmer must explicitly qualify the call with the appropriate namespace name to get the g() he wants.

You may at first wonder why this should be so. After all, as with f(), B hasn't written "using" anywhere to bring A::g(X) into its scope, so you'd think that only B::g(X) would be visible, right? Well, this would be true except for an extra rule that C++ uses when looking up names:

Koenig Lookup[1] (simplified): If you supply a function argument of class type (here x, of type A::X), then to look up the correct function name the compiler considers matching names in the namespace (here A) containing the argument's type.

[1] Named after Andrew Koenig, who nailed down its definition and is a long-time member of both AT&T's C++ team and the C++ standards committee. See also Koenig97.

There's a little more to it, but that's essentially it. Here's an example, right out of the standard.



namespace NS 


{


  class T { };


  void f(T);


}


NS::T parm;


int main()


{


  f(parm);    // OK, calls NS::f


}


I won't delve here into the reasons why Koenig lookup is a Good Thing (if you want to stretch your imagination right now, take the above code and replace "NS" with "std", "T" with "string", and "f" with "operator<<" and consider the ramifications). See the next Item for much more on Koenig lookup and its implications for namespace isolation and for analyzing class dependencies.

Suffice it to say that Koenig lookup is, indeed, a Good Thing and that you should be aware of how it works, because it can sometimes affect the code you write.

Now back to a simple example:



  void h( A::Y y ) 


  {


    h( y );   // which h()?


  }


}


There is no other h( A::Y ), so this h() calls itself with infinite recursion.

Although B::h()'s signature mentions a type found in namespace A, this doesn't affect name lookup because there are no functions in A matching the name and signature h(A::Y).

So, what does it mean? That brings us to the last part of our Item question: Analyze the implications.

In short, the meaning of code in namespace B is being affected by a function declared in the completely separate namespace A, even though B has done nothing but simply mention a type found in A and there's nary a "using" in sight.

What this means is that namespaces aren't quite as independent as people originally thought. Don't start denouncing namespaces just yet, though; namespaces are still pretty independent, and they fit their intended uses to a T. The purpose of this Item is just to point out one of the (rare) cases in which namespaces aren't quite hermetically sealed梐nd, in fact, in which they should not be hermetically sealed, as the following Item will show.

Recall a traditional definition of a class:

A class describes a set of data, along with the functions that operate on that data.

Our question is: What functions are "part of " a class, or make up the interface of a class?

    I l@ve RuBoard Previous Section Next Section