Why does C++'s "using namespace" work the way it does?
Asked Answered
F

1

37

All students are surprised by the behavior of C++ using-directives. Consider this snippet (Godbolt):

namespace NA {
    int foo(Zoo::Lion);
}
namespace NB {
    int foo(Zoo::Lion);
    namespace NC {
        namespace N1 {
            int foo(Zoo::Cat);
        }
        namespace N2 {
            int test() {
                using namespace N1;
                using namespace NA;
                return foo(Zoo::Lion());
            }
        }
    }
}

You might think test would call NA's foo(Zoo::Lion); but in fact it ends up calling N1's foo(Zoo::Cat).

The reason is that using namespace NA doesn't actually bring the names from NA into the current scope; it brings them into the least common ancestor scope of NA and N2, which is ::. And using namespace N1 doesn't bring the names from N1 into the current scope; it brings them into the least common ancestor scope of N1 and N2, which is NC. Here, I've drawn a pretty diagram:

Diagram

Then normal unqualified lookup "looks up" the tree, in the order testN2NCNB::, stopping as soon as it finds the name foo in NC. The foos higher up the tree, in NB and ::, aren't found by lookup because they've been hidden by the foo in NC.

I think I understand this mechanism well enough to explain how it works. What I don't understand is why. When C++ namespaces were being designed, why did they choose this utterly weird mechanism, instead of something more straightforward like "The names are brought into the current scope just like with a using-declaration" or "The names are brought into the global scope" or "The names are left where they are, separate lookups are done in each 'used' namespace and then merged together"?

What considerations went into the choice of this particular mechanism for C++?

I'm looking specifically for references to the design of C++ — books, blog posts, WG21 papers, D&E, ARM, reflector discussion, anything of that nature. I am specifically not looking for "opinion-based" answers; I'm looking for the real rationale specified at the time the feature was designed.

(I've read The Design and Evolution of C++ (1994), section 17.4 "Namespaces," but it doesn't even mention this wacky mechanism. D&E says: "A using-directive doesn't introduce names into the local scope; it simply makes the names from the namespace accessible." Which is true, but rather lacking in rationale; I'm hoping Stack Overflow can do better.)

Fowling answered 19/12, 2020 at 0:53 Comment(2)
If Stroustrup doesn't explain his reasoning, the reasoning can only be inferred. I'm not sure you can get a perfect answer for this, but I'm more than willing to leave this one open and see what people come up with.Preamplifier
One could engage in ISO C++ Standard - DiscussionLaomedon
S
2

Quoting an excerpt from C++ Primer (5th Edition) section "18.2.2. Using Namespace Members"

The scope of names introduced by a using directive is more complicated than the scope of names in using declarations. As we’ve seen, a using declaration puts the name in the same scope as that of the using declaration itself. It is as if the using declaration declares a local alias for the namespace member.

A using directive does not declare local aliases. Rather, it has the effect of lifting the namespace members into the nearest scope that contains both the namespace itself and the using directive.

This difference in scope between a using declaration and a using directive stems directly from how these two facilities work. In the case of a using declaration, we are simply making name directly accessible in the local scope. In contrast, a using directive makes the entire contents of a namespace available In general, a namespace might include definitions that cannot appear in a local scope. As a consequence, a using directive is treated as if it appeared in the nearest enclosing namespace scope.

So this difference in scope handling of using-directives compared to using-declarations is to not prevent identical names from being declared. When such name conflict occurs it will be permitted but any ambiguous usage must explicitly specify the intended version.

Selfeffacing answered 28/12, 2020 at 13:38 Comment(2)
I might be asking for original research with this, but, what might the author have meant by "a namespace might include definitions that cannot appear in a local scope"? It seems like they're saying there are things pullable-in-by using namespace that can't be pulled in by a using-declaration. The only thing I can think of that can't appear at local scope is templates, like in godbolt.org/z/EjT8Eo — but we can drag in templates just fine via either using-declarations or using-directives.Fowling
...and immediately I realize, a nested namespace declaration can be pulled in by using namespace N1 but not by using N1::N2! (There's a different syntax for namespace aliases: namespace N2 = N1::N2.) I still don't understand the "As a consequence" bit, though.Fowling

© 2022 - 2024 — McMap. All rights reserved.