4

There are two types of name hiding in c++:

1) Normal name hiding: [basic.scope.hiding]p1 (http://eel.is/c++draft/basic.scope.hiding#1):

A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived class ([class.member.lookup]).

2) The special type of name hiding in [basic.scope.hiding]p2 (http://eel.is/c++draft/basic.scope.hiding#2):

A class name ([class.name]) or enumeration name ([dcl.enum]) can be hidden by the name of a variable, data member, function, or enumerator declared in the same scope. If a class or enumeration name and a variable, data member, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the variable, data member, function, or enumerator name is visible.

I'm interested to know about how name hiding interacts with using-directives when unqualified name lookup is performed.

For the first type of name hiding the behaviour is quite clear. This is because [basic.scope.hiding]p1 has been reformulated in terms of the rules in the section [basic.lookup.unqual] (http://eel.is/c++draft/basic.lookup.unqual)

The same has not been done for the second type of name hiding. So the following question now arises:

*) How should this second type of name hiding interact with unqualified name lookup that involves using-directives?

Looking elsewhere in the standard I find [namespace.udir]p2 (http://eel.is/c++draft/namespace.udir#2) and I think this is key to answering this question:

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 ]

Applying the as if part of this rule to [basic.scope.hiding]p1 gives consistency with the rules in the section [basic.lookup.unqual]. This application is also consistent with [basic.scope.hiding]p4 (http://eel.is/c++draft/basic.scope.hiding#4) So this looks promising.

Because of this I think we can answer the question *) by similarly applying the as if part of [namespace.udir]p2 to [basic.scope.hiding]p2. This application is also consistent with [basic.scope.hiding]p4. I think this is also the most natural and least complex interpretation of the c++ standard.

The problem however is that Clang and GCC does not make the same interpretation as me. For example:

namespace N { static int i = 1; }
namespace M { struct i {}; }
using namespace M;
using namespace N;    
int main() { sizeof(i); }

According to my interpretation this program should be well-formed and i should be looked up as the integer variable. Both Clang and GCC disagree with this by giving a name lookup ambiguity.

In the case of Clang this more complex interpretation leads to the following bug:

namespace N { static int i = 1; }
namespace M { struct i {}; }
namespace P {
    using N::i;
    using M::i;
}
namespace Q { using M::i; }
using namespace P;
using namespace Q;
int main() { sizeof (i); }

Gives no errors, but change

using namespace P;
using namespace Q;

into

using namespace Q;
using namespace P;

and we get name-lookup ambiguity error. GCC is at least consistent here.

Did I interpret the c++ standard correctly?

Supremum
  • 542
  • 7
  • 23

2 Answers2

3

The key phrases here I believe are:

A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived class (10.2).

A class name (9.1) or enumeration name (7.2) can be hidden by the name of a variable, data member, function, or enumerator declared in the same scope.

In this example:

namespace N { static int i = 1; }
namespace M { struct i {}; }
using namespace M;
using namespace N;    
int main() { sizeof(i); }

Both is are declared in different, non-nested scopes, so there is no hiding. Name lookup finds them as if they were declared in ::, but that's not what the rule for hiding stipulates.

Otherwise, we have, from [basic.lookup]:

Name lookup shall find an unambiguous declaration for the name (see 10.2). Name lookup may associate more than one declaration with a name if it finds the name to be a function name;

There is no unambiguous declaration in ::, so this code is ill-formed and the error is correct. The same is true for the other example, so the fact that there is some using-declaration ordering for which clang compiles it is a bug.

While this is non-normative, there is an example in [namespace.udir] that makes this interpretation clear:

[ Note: In particular, the name of a variable, function or enumerator does not hide the name of a class or enumeration declared in a different namespace. For example,

namespace A {
    class X { };
    extern "C" int g();
    extern "C++" int h();
}

namespace B {
    void X(int);
    extern "C" int g();
    extern "C++" int h(int);
}

using namespace A;
using namespace B;
void f() {
   X(1); // error: name X found in two namespaces
   g();  // OK: name g refers to the same entity
   h();  // OK: overload resolution selects A::h
}

—end note ]

Barry
  • 286,269
  • 29
  • 621
  • 977
  • @Supremum Declared means declared. The example illustrates that the function `X` declared in `B` does not hide the class `X` declared in `A`. The using-declarations just mean that `X` is found in `::`. – Barry Jul 29 '15 at 16:06
  • What does declared in different namespaces mean exactly? What about using-declarations? For example clang accepts namespace N { static int i = 1; } namespace M { struct i {}; } using N::i; using M::i; int main() { sizeof (i); } but rejects with ambiguity if changing using-declaration orders. I guess only namespaces where it was originally declared counts (not declaration via using-declarations), so clang should reject in both cases. – Supremum Jul 29 '15 at 16:15
  • When people are talking about declared I am always confused because of using-declarations. – Supremum Jul 29 '15 at 16:17
  • Ok, so using-declarations are only introducing names into the declarative region they occur in, but they are not declaring anything. They just refer to the declaration of an entity made by a non-using-declaration (possibly indirectly via other using-delcarations). Is that right? – Supremum Jul 29 '15 at 16:57
  • I really don't like the fact that the c++ standard present "as if" rules. You apply them and think that you have deduced something, but then some other place of the standard impose a restriction that prevents that particular application of the "as if" rule. And that other place could be very far away from the presentation of the "as if" rule. In this case it doesn't help either that the section [basic.lookup.unqual] does not cover [basic.scope.hiding]p2, but only [basic.scope.hiding]p1. – Supremum Jul 29 '15 at 17:11
2
namespace N { static int i = 1; }
namespace M { struct i {}; }
using namespace M;
using namespace N;    
int main() { sizeof(i); }

This is ill-formed. §7.3.4/6:

If name lookup finds a declaration for a name in two different namespaces, and the declarations do not declare the same entity and do not declare functions, the use of the name is ill-formed.

The same applies to your second example. Note that the name hiding rule that applies in e.g.

struct A {} A;

…doesn't apply in your case, as the two is are declared in distinct scopes. Also,

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.

Is irrelevant as well since any ambiguity that name lookup produces, as in your examples with i, is dealt with after lookup - here in e.g. the aforementioned §7.3.4/6.

Columbo
  • 60,038
  • 8
  • 155
  • 203