Discussion
Team LiB
Previous Section Next Section

Discussion

By following this advice, you will avoid having to track down hard-to-diagnose errors in your code and avoid having to know about excessively subtle language details that you, well, should never have to know about.

Consider this actual example that was posted publicly to a newsgroup:



#include <vector>





namespace N {


  struct X {};





  template<typename T>


  int* operator+( T , unsigned ) {/* do something */}


}





int main() {


  std::vector<N::X> v(5);


  v[0] ;


}



The statement v[0]; compiles on some standard library implementations but not on others. To make a very long story moderately less long (take a deep breath): The exceedingly subtle problem is that inside most implementations of vector<T>::operator[] lurks code like v.begin() + n, and the name lookup for that operator+ function might reach out into the namespace (here N) of the type that vector is instantiated with (here X). Whether it reaches out into N like that depends on how vector<T>::iterator happens to be defined in that release of that standard library implementationbut if it does look into N, then it will find N::operator+. Finally, depending on the types involved, the compiler might just discover that N::operator+ is a better match than the std::operator+ for vector<T>::iterators that was provided (and intended to be called) in that standard library implementation. (One way that the standard library implementation could protect itself from this is to not write code like v.begin() + n in that way, which injects an unintentional point of customization: Either arrange for v.begin()'s type to not depend in any way on the template parameter, or rewrite the call to operator+ as a qualified call. See Item 65)

In short, you'll almost certainly never figure out what's going on from the error messageif you're lucky enough to get an error message, that is, because you might happen to hit the worst of all possible worlds where N::operator+ is chosen but unfortunately turns out to be compilable, although completely unintended and wildly wrong.

If you think you haven't been bit by this, just think back: Can you remember a time when you wrote code that used the standard library (for example) and got mysterious and incomprehensible compiler errors? And you kept slightly rearranging your code and recompiling, and rearranging some more and compiling some more, until the mysterious compile errors went away, and then you happily continued onwith at best a faint nagging curiosity about why the compiler didn't like the only-ever-so-slightly different arrangement of the code you wrote at first? We've all had those days, and the odds are decent that the mystery culprit was some form of the aforementioned problem, where ADL pulled in names from other namespaces inappropriately just because types from those namespaces were being used nearby.

This problem is not unique to uses of the standard library. It can and does happen in C++ with the use of any type that is defined in the same namespace as functionsespecially templated functions, and most especially operatorsthat aren't specifically related to that type. Don't do that.

Bottom line: You shouldn't have to know this stuff. The easiest way to avoid this whole category of problems is to in general avoid putting nonmember functions that are not part of the interface of a type X into the same namespace as X, and especially never ever put templated functions or operators into the same namespace as a user-defined type.

Note: Yes, the C++ standard library puts algorithms and other function templates, such as copy and distance, into the same namespace as lots of types, such as pair and vector. It puts everything into a single namespace. That's unfortunate, and it causes exactly these kinds of very subtle problems. We know better now. Learn from the past. Don't do that.

For the flip side of this issue, see Item 57.

    Team LiB
    Previous Section Next Section