Helloer
Helloer

Reputation: 467

What are the name resolution rules in C++ namespaces?

How do names get resolved in C++ namespace?

I'm especially interested in cases where a name is provided in multiple different ways, for instance by a parent namespace and a using namespace.

Take this piece of code:

namespace ns1 {
    static auto str = "ns1::str";
}
namespace ns2 {
    static auto str = "ns2::str";
    namespace sub {
        using namespace ns1;
        auto f() { return str; }
    }
}

With my compiler, ns2::sub::f() returns "ns2::str". I expected it to return "ns1::str", because using namespace ns1 appears in ns2::sub.

And now this piece:

static auto str = "str";
namespace ns1 {
    static auto str = "ns1::str";
}
namespace ns2 {
    using namespace ns1;
    auto f() { return str; }
}

I expected it to behave like the previous case. Instead it doesn't compile:

error: reference to ‘str’ is ambiguous

What's the logic behind this?

Upvotes: 3

Views: 393

Answers (1)

The rules to keep in mind for using-directives are the following

[namespace.udir]

2 A using-directive specifies that the names in the nominated namespace can be used in the scope in which the using-directive appears after the using-directive. During unqualified name lookup ([basic.lookup.unqual]), the names appear as if they were declared in the nearest enclosing namespace which contains both the using-directive and the nominated namespace. [ Note: In this context, “contains” means “contains directly or indirectly”. — end note ]

3 A using-directive does not add any members to the declarative region in which it appears.

Paragraph 3 is what ensures that we don't get an error until name lookup is attempted. It doesn't add a declaration, and so long as str isn't looked up by unqualified name lookup, we get no conflict. Whereas paragraph 2 tells us how the names are logically laid out. We can apply it to your two examples.

  1. The nearest enclosing namespace that contains both the using namespace ns1; and the namespace ns1 itself is the global namespace. So the name ns1::str behaves as if we had written

    static auto str = "ns1::str";
    namespace ns2 {
       static auto str = "ns2::str";
        namespace sub {
            auto f() { return str; }
        }
    }
    

    So, name lookup would only find ns2::str, because it would hide any str from and enclosing namespace (which is the "as-if" situation in the code above).

  2. Again, the nearest enclosing namespace is the global one. But this time, the layout we get is different

    static auto str = "str"; // This is the one declared
    static auto str = "ns1::str"; // This is the one "as-if" made available
    
    namespace ns2 {
        auto f() { return str; }
    }
    

    Obviously, the above "equivalent" code snippet isn't a valid one, the declarations would be conflicting ones. But it is an illustration to the source of the conflict. The name coming from that scope is in conflict. So unqualified name lookup fails.

In the absence of using-directives, unqualified name lookup behaves in a fairly intuitive manner. Scopes are looked up from inner to outer, until a declaration for a name is found.

Upvotes: 2

Related Questions