Edge case of name lookup with using directives and using-declarations

23 views
Skip to first unread message

Anders Granlund

unread,
Jul 30, 2015, 12:57:50 PM7/30/15
to ISO C++ Standard - Discussion
Should this be well-formed or ill-formed according to the c++ standard:

namespace A { static int i = 1; }
namespace B { struct i {}; }
namespace C { using A::i; using B::i; }
namespace D { using A::i; }
using namespace C;
using namespace D;
int main() { sizeof (i); }

Unqualified name-lookup of i in main finds two declarations of names in the namespace C:

(1) The declaration of the name i to denote the integer variable. This declaration is made by using A::i; 
(2) The declaration of the name i to denote the struct. This declaration is made by using B::i;

The unqualified name-lookup also finds a declaration of a name in the namespace D:

(3) The declaration of the name i to denote the integer variable. This declaration is made by in using A::i;

Now look at [namespace.udir]p6 (http://eel.is/c++draft/dcl.dcl#namespace.udir-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."

Should (2) and (3) count as a violation of this or is this not a violation because of [basic.scope.hiding]p2 http://eel.is/c++draft/basic.scope.hiding#2 applied to (1) and (2)?

Johannes Schaub

unread,
Jul 30, 2015, 1:03:36 PM7/30/15
to std-dis...@isocpp.org
2015-07-30 18:57 GMT+02:00 Anders Granlund <anders.g...@gmail.com>:
> Should this be well-formed or ill-formed according to the c++ standard:
>
> namespace A { static int i = 1; }
> namespace B { struct i {}; }
> namespace C { using A::i; using B::i; }
> namespace D { using A::i; }
> using namespace C;
> using namespace D;
> int main() { sizeof (i); }
>
> Unqualified name-lookup of i in main finds two declarations of names in the
> namespace C:
>
> (1) The declaration of the name i to denote the integer variable. This
> declaration is made by using A::i;
> (2) The declaration of the name i to denote the struct. This declaration is
> made by using B::i;
>
> The unqualified name-lookup also finds a declaration of a name in the
> namespace D:
>
> (3) The declaration of the name i to denote the integer variable. This
> declaration is made by in using A::i;
>
> Now look at [namespace.udir]p6
> (http://eel.is/c++draft/dcl.dcl#namespace.udir-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."
>

You find two declarations of the same variable. And albeit those
declarations (both aliases) are in different namespaces they declare
the same entity so I think that the text you quoted is not violated.

Anders Granlund

unread,
Jul 30, 2015, 1:10:05 PM7/30/15
to std-dis...@isocpp.org
It is not that simple. I find three declarations (1), (2) and (3). I'm not worried about (1) and (3), I'm worried about (2) and (3).


--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Johannes Schaub

unread,
Jul 30, 2015, 1:13:10 PM7/30/15
to std-dis...@isocpp.org
You don't find (2) because it is hidden by (1), according to 3.3.10p2.

Anders Granlund

unread,
Jul 30, 2015, 1:48:12 PM7/30/15
to std-dis...@isocpp.org
Ok, then I understand. If we had sizeof (struct S); instead we would only find (2), but not (1) and (3), so no problem then either.

I have however another edge case:

namespace N { struct S; typedef S S; }

namespace P { using N::S; }
namespace Q { using N::S; }

int main() { sizeof (); }

In namespace P:

(1) Declaration of the name S as a struct name. (Done by using N::S;)
(2) Declaration of the name S as a type name. (Also done by using N::S;)
 
In namespace Q:

(3) Declaration of the name S as a struct name. (Done by using N::S;)
(4) Declaration of the name S as a type name. (Also done by using N::S;)

What about (1) and (4) there? Violation of [namespace.udir]p6 (http://eel.is/c++draft/dcl.dcl#namespace.udir-6)?

Johannes Schaub

unread,
Jul 30, 2015, 2:17:52 PM7/30/15
to std-dis...@isocpp.org
2015-07-30 19:48 GMT+02:00 Anders Granlund <anders.g...@gmail.com>:
> Ok, then I understand. If we had sizeof (struct S); instead we would only
> find (2), but not (1) and (3), so no problem then either.
>
> I have however another edge case:
>
> namespace N { struct S; typedef S S; }
>
> namespace P { using N::S; }
> namespace Q { using N::S; }
>
> int main() { sizeof (); }
>
> In namespace P:
>
> (1) Declaration of the name S as a struct name. (Done by using N::S;)
> (2) Declaration of the name S as a type name. (Also done by using N::S;)
>
> In namespace Q:
>
> (3) Declaration of the name S as a struct name. (Done by using N::S;)
> (4) Declaration of the name S as a type name. (Also done by using N::S;)
>
> What about (1) and (4) there? Violation of [namespace.udir]p6
> (http://eel.is/c++draft/dcl.dcl#namespace.udir-6)?
>

I think you forgot the using directives above. Assuming you meant to
write them, your analysis about the declared names is slightly
incorrect. There is only a single name "P::S" and only a single name
"Q::S" and only a single name "N::S" declared. The "typedef S S;" does
not introduce a second name, but redefines the already introduced
name, to be both a class-name and a typedef-name.

And even if there would be multiple names found by namelookup, it does
not violate the referred paragraph, because both would refer to the
same entity (of kind 'type').

Johannes Schaub

unread,
Jul 30, 2015, 2:26:09 PM7/30/15
to std-dis...@isocpp.org
BTW here is a fun experiment:

struct A {
struct B { };
private:
typedef B B;
};

int main() {
A::B b;
}

Perhaps it's "ill-formed; NDR" so a compiler can accept or reject it
on its behalf I, because the name B first refers to the first
declaration but then the name is redefined by the second declaration
and so in the complete scope of "A" it would refer to the second
declaration.

What is fact is that GCC and Clang disagree. GCC allows, Clang rejects.

Johannes Schaub

unread,
Jul 30, 2015, 2:31:32 PM7/30/15
to std-dis...@isocpp.org
In fact we can get rid of the unclear interpretation by adding a layer
of indirection. Then I think that the snippet must be rejected because
the redefining declaration is inaccessible, but GCC still allows. Not
sure why:

struct A {
struct B { };
typedef B C;

private:
typedef C B;
};

Anders Granlund

unread,
Jul 30, 2015, 2:33:46 PM7/30/15
to std-dis...@isocpp.org
Yes I forgot the using-directives.

Hmm... interesting. This compiles on both clang and gcc, so that seem to suggest that you are correct about type aliases and struct names can denote the same entity:

namespace N { struct S {}; }
namespace M { using S = N::S; }
using namespace N;
using namespace M;

int main() { sizeof(S); }

Since S in N and S in M denotes the same entity, the struct type.

The same does not seems to hold for namespaces vs namespace aliases (at least not according clang and gcc):

namespace P{ namespace X {} }
namespace Q { namespace X = P::X; }
using namespace P;
using namespace Q;
namespace P = Q;
int main() {  }

Why this inconsistency?


--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.

Anders Granlund

unread,
Jul 30, 2015, 2:34:34 PM7/30/15
to std-dis...@isocpp.org
Sorry should be this instead in my second example:

namespace P{ namespace X {} }
namespace Q { namespace X = P::X; }
using namespace P;
using namespace Q;
namespace P = X;
int main() {  }

Johannes Schaub

unread,
Jul 30, 2015, 2:57:47 PM7/30/15
to std-dis...@isocpp.org
2015-07-30 20:33 GMT+02:00 Anders Granlund <anders.g...@gmail.com>:
> Yes I forgot the using-directives.
>
> Hmm... interesting. This compiles on both clang and gcc, so that seem to
> suggest that you are correct about type aliases and struct names can denote
> the same entity:
>
> namespace N { struct S {}; }
> namespace M { using S = N::S; }
> using namespace N;
> using namespace M;
>
> int main() { sizeof(S); }
>
> Since S in N and S in M denotes the same entity, the struct type.
>
> The same does not seems to hold for namespaces vs namespace aliases (at
> least not according clang and gcc):
>
> namespace P{ namespace X {} }
> namespace Q { namespace X = P::X; }
> using namespace P;
> using namespace Q;
> namespace P = Q;
> int main() { }
>
> Why this inconsistency?
>
>

I don't know, but perhaps the actual rules are a bit stricter than
what merely that all declarations must declare the same entity. After
all, name lookup is more than only finding entities. It's about
whether a class-name, typedef-name, etc was found. And 3.4p1 says

"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;".

As to why typedefs seem to be a special case and compilers don't
complain for them - they and using-declarations seem to be replaced by
their targets and therefore have a special status. At least that
happens for class member name lookup. The spec says for class member
name lookup (which BTW changed from C++03 to C++11 and the popular
compilers don't implement the C++11 rules yet): " In the declaration
set, using-declarations are replaced by the members they designate,
and type declarations (including injected-class-names) are replaced by
the types they designate.".

That the end result of name lookup must still have some notion of
track of where the found declaration came from must be clear. After
all, accessibility checks (which happens after name lookup) must be
able to verify the access path (but how, if the lookup process got rid
of all intermediate declarations!?), and it's beyond me how a "typedef
int haha;" can be replaced by "int" in the declaration set. Perhaps
that's why these rules seem to have stayed unimplemented.

Anders Granlund

unread,
Jul 30, 2015, 2:58:40 PM7/30/15
to std-dis...@isocpp.org
Hmm, interesting example with different visibility. If we use an elaborated type specifier (they can't be used with typedef name, only with class names) both compilers agree that the program is ok:

struct A {
  struct B { };
private:
  typedef B B;
};

int main() {
   struct A::B b;
}

I don't think the indirection in your second example adds much.

I think you should file a core defect about the example you found because I don't think the standard is very clear in this case.



Johannes Schaub

unread,
Jul 30, 2015, 3:07:39 PM7/30/15
to std-dis...@isocpp.org
2015-07-30 20:58 GMT+02:00 Anders Granlund <anders.g...@gmail.com>:
> Hmm, interesting example with different visibility. If we use an elaborated
> type specifier (they can't be used with typedef name, only with class names)
> both compilers agree that the program is ok:
>
> struct A {
> struct B { };
> private:
> typedef B B;
> };
>
> int main() {
> struct A::B b;
> }
>
> I don't think the indirection in your second example adds much.
>
> I think you should file a core defect about the example you found because I
> don't think the standard is very clear in this case.
>

I think the following rule of the spec is intended to prevent this:
"When a member is redeclared within its class definition, the access
specified at its redeclaration shall be the
same as at its initial declaration.". But GCC and Clang don't
complain. I guess because the spec doesn't seem to count this as
"redeclaring" the member: " A member shall not be declared twice in
the member-specification, except that a nested class or member class
template can be declared and then later defined, ...". (otherwise it
would declare ill-formed a construct that it itself allows elsewhere).

I think that this is broken.

Anders Granlund

unread,
Jul 30, 2015, 3:15:14 PM7/30/15
to std-dis...@isocpp.org
Hmm, interesting. I have some stuff to do now, but i'll think about this some more later.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.

Anders Granlund

unread,
Jul 30, 2015, 4:42:59 PM7/30/15
to std-dis...@isocpp.org
Yeah. I agree with you, that seems to be the most reasonable solution. I think you should file a core language issue for this. Both Clang and GCC should be fixed if it turns out that this is in fact a core language defect, since they both accept your example if we leave out the name-lookup part of it (by just leaving the body of main empty).


--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.

Anders Granlund

unread,
Jul 30, 2015, 5:51:07 PM7/30/15
to std-dis...@isocpp.org
2015-07-30 20:57 GMT+02:00 'Johannes Schaub' via ISO C++ Standard - Discussion <std-dis...@isocpp.org>:
2015-07-30 20:33 GMT+02:00 Anders Granlund <anders.g...@gmail.com>:
> Yes I forgot the using-directives.
>
> Hmm... interesting. This compiles on both clang and gcc, so that seem to
> suggest that you are correct about type aliases and struct names can denote
> the same entity:
>
> namespace N { struct S {}; }
> namespace M { using S = N::S; }
> using namespace N;
> using namespace M;
>
> int main() { sizeof(S); }
>
> Since S in N and S in M denotes the same entity, the struct type.
>
> The same does not seems to hold for namespaces vs namespace aliases (at
> least not according clang and gcc):
>
> namespace P{ namespace X {} }
> namespace Q { namespace X = P::X; }
> using namespace P;
> using namespace Q;
> namespace P = Q;

Correction: Should have been  namespace P = X;  instead of namespace P = Q;
 
> int main() {  }
>
> Why this inconsistency?
>
>

I don't know, but perhaps the actual rules are a bit stricter than
what merely that all declarations must declare the same entity. 
 
Maybe I missed some special case. I'll check in the standard for that.

After
all, name lookup is more than only finding entities. It's about
whether a class-name, typedef-name, etc was found. And 3.4p1 says


I think you are right about that. Actually the same for namespace/namespace aliases. Looking in the standard I see that there are some rules in the standard that rely on weather or not a name is a namespace name or namespace alias name even if they denote the same entity (same namespace). So yes, it seems like the entity denoted isn't everything, there are also the concept of the kind of name we have (typedefname vs class name, namespace alias name vs namespace name). Thanks for your reply, this explains a lot actually.

I actually found a special rule about namespace aliases in name-lookup http://eel.is/c++draft/basic.lookup.udir#1:

"In a using-directive or namespace-alias-definition, during the lookup for a namespace-name or for a name in a nested-name-specifier only namespace names are considered."

Seems like clang and gcc have a bug regarding this. For example they both accept this:

namespace P {}
namespace X = P;
namespace Y = X;
int main() {}

They both should reject the program since they should not be able to lookup X in namespace Y = X; . I'll file a bug report for this. (I'll test the using-directive case also later).

Lets change my example to avoid this bug and see what happens:

#include <iostream>
namespace P{ namespace X { static int i = 1; } }

namespace Q { namespace X = P::X; }
using namespace P;
using namespace Q;
int main() { std::cout << X::i << std::endl; }

Still name lookup ambiguity for X. Maybe another bug in gcc and clang? I'll check the standard, maybe I missed something.

"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;".

As to why typedefs seem to be a special case and compilers don't
complain for them - they and using-declarations seem to be replaced by
their targets and therefore have a special status. At least that
happens for class member name lookup. The spec says for class member
name lookup (which BTW changed from C++03 to C++11 and the popular
compilers don't implement the C++11 rules yet): " In the declaration
set, using-declarations are replaced by the members they designate,
and type declarations (including injected-class-names) are replaced by
the types they designate.".

That the end result of name lookup must still have some notion of
track of where the found declaration came from must be clear. After
all, accessibility checks (which happens after name lookup) must be
able to verify the access path (but how, if the lookup process got rid
of all intermediate declarations!?), and it's beyond me how a "typedef
int haha;" can be replaced by "int" in the declaration set. Perhaps
that's why these rules seem to have stayed unimplemented.


Yeah could be a difference in 

Richard Smith

unread,
Jul 30, 2015, 6:06:58 PM7/30/15
to std-dis...@isocpp.org
On Thu, Jul 30, 2015 at 2:51 PM, Anders Granlund <anders.g...@gmail.com> wrote:
2015-07-30 20:57 GMT+02:00 'Johannes Schaub' via ISO C++ Standard - Discussion <std-dis...@isocpp.org>:
2015-07-30 20:33 GMT+02:00 Anders Granlund <anders.g...@gmail.com>:
> Yes I forgot the using-directives.
>
> Hmm... interesting. This compiles on both clang and gcc, so that seem to
> suggest that you are correct about type aliases and struct names can denote
> the same entity:
>
> namespace N { struct S {}; }
> namespace M { using S = N::S; }
> using namespace N;
> using namespace M;
>
> int main() { sizeof(S); }
>
> Since S in N and S in M denotes the same entity, the struct type.
>
> The same does not seems to hold for namespaces vs namespace aliases (at
> least not according clang and gcc):
>
> namespace P{ namespace X {} }
> namespace Q { namespace X = P::X; }
> using namespace P;
> using namespace Q;
> namespace P = Q;

Correction: Should have been  namespace P = X;  instead of namespace P = Q;

I don't think so; I think you mean something like "namespace R = X;". "namespace P = X;" is obviously ill-formed, because there's already a different namespace ::P.

A simpler testcase:

  namespace N {}
  namespace N = N;

Clang, GCC, and EDG all reject this too, because they apparently think the two declarations are declaring different entities.

> int main() {  }
>
> Why this inconsistency?
>
>

I don't know, but perhaps the actual rules are a bit stricter than
what merely that all declarations must declare the same entity. 
 
Maybe I missed some special case. I'll check in the standard for that.

After
all, name lookup is more than only finding entities. It's about
whether a class-name, typedef-name, etc was found. And 3.4p1 says


I think you are right about that. Actually the same for namespace/namespace aliases. Looking in the standard I see that there are some rules in the standard that rely on weather or not a name is a namespace name or namespace alias name even if they denote the same entity (same namespace). So yes, it seems like the entity denoted isn't everything, there are also the concept of the kind of name we have (typedefname vs class name, namespace alias name vs namespace name). Thanks for your reply, this explains a lot actually.

I actually found a special rule about namespace aliases in name-lookup http://eel.is/c++draft/basic.lookup.udir#1:

"In a using-directive or namespace-alias-definition, during the lookup for a namespace-name or for a name in a nested-name-specifier only namespace names are considered."

Seems like clang and gcc have a bug regarding this. For example they both accept this:

namespace P {}
namespace X = P;
namespace Y = X;
int main() {}

They both should reject the program since they should not be able to lookup X in namespace Y = X; .

Why not? X is a namespace name. (The standard seems to fail to say that a namespace-alias-definition introduces a namespace-name, but that's clearly the intent; see [namespace.def]p3's reference to "a namespace-name (but not a namespace-alias)", and in any case the phrase "namespace name" is not referencing the grammar term namespace-name, and so can be understood to mean "name that names a namespace").

Anders Granlund

unread,
Jul 30, 2015, 6:30:31 PM7/30/15
to std-dis...@isocpp.org
Sorry for the confusion. Actually I meant this:

namespace P{ namespace X {} }
namespace Q { namespace X = P::X; }
using namespace P;
using namespace Q;
namespace Y = X;
int main() {}

But a better example is (the last namespace alias definition was not needed and only complicate things):

namespace P{ namespace X { static int i = 1; } }
namespace Q { namespace X = P::X; }
using namespace P;
using namespace Q;
int main() { X::i; }

Both Clang and GCC incorrectly rejects this program. It is well-formed right? The namespace X and the namespace alias X refers to the same entity (the namespace X) so we should not get any name-lookup ambiguity here.


> int main() {  }
>
> Why this inconsistency?
>
>

I don't know, but perhaps the actual rules are a bit stricter than
what merely that all declarations must declare the same entity. 
 
Maybe I missed some special case. I'll check in the standard for that.

After
all, name lookup is more than only finding entities. It's about
whether a class-name, typedef-name, etc was found. And 3.4p1 says


I think you are right about that. Actually the same for namespace/namespace aliases. Looking in the standard I see that there are some rules in the standard that rely on weather or not a name is a namespace name or namespace alias name even if they denote the same entity (same namespace). So yes, it seems like the entity denoted isn't everything, there are also the concept of the kind of name we have (typedefname vs class name, namespace alias name vs namespace name). Thanks for your reply, this explains a lot actually.

I actually found a special rule about namespace aliases in name-lookup http://eel.is/c++draft/basic.lookup.udir#1:

"In a using-directive or namespace-alias-definition, during the lookup for a namespace-name or for a name in a nested-name-specifier only namespace names are considered."

Seems like clang and gcc have a bug regarding this. For example they both accept this:

namespace P {}
namespace X = P;
namespace Y = X;
int main() {}

They both should reject the program since they should not be able to lookup X in namespace Y = X; .

Why not? X is a namespace name. (The standard seems to fail to say that a namespace-alias-definition introduces a namespace-name, but that's clearly the intent; see [namespace.def]p3's reference to "a namespace-name (but not a namespace-alias)", and in any case the phrase "namespace name" is not referencing the grammar term namespace-name, and so can be understood to mean "name that names a namespace").

 Yes you are right.

Anders Granlund

unread,
Jul 30, 2015, 7:03:25 PM7/30/15
to std-dis...@isocpp.org
I see what you mean, the root cause for this bug is that the compilers doesn't consider namespace alias names and normal namespace names to refer to the same entity, even when they do.   

Anders Granlund

unread,
Jul 30, 2015, 7:28:44 PM7/30/15
to std-dis...@isocpp.org
Btw isn't a  namespace-alias-definition  a definition?

Anders Granlund

unread,
Jul 30, 2015, 8:00:35 PM7/30/15
to std-dis...@isocpp.org
Nevermind ODR does not apply to namespace-alias-definitions anyway.

Reply all
Reply to author
Forward
0 new messages