Repository navigation
CWG2634 [dcl.type.elab] p3 The nominated E is not clear in the rule such that implementations have a divergence #80
Description
Activity
- changed the title
[-][dcl.type.elab] p3 The nominated E is not clear in the rule[/-][+][dcl.type.elab] p3 The nominated E is not clear in the rule such that implementations have a divergence[/+]on Jul 4, 2022 A simplified example is more helpful to expose the issue here.
auto fun(struct X* ptr){ // #1 struct D{ private: int d; friend class X; // #2 unqualified name `X` }; return D{}; } X* b = 0; struct X{ void show(){ auto t = fun(0); t.d = 10; } };
The unqualified name
Xat#1founds nothing, hence the elaborated-type-specifier introduces class-nameXand the target scope of this declaration is the nearest enclosing namespace scope, as per the current wording in [dcl.type.elab] p3.However, the unqualified name at
#2is a bit unclear here. The unqualified name lookup can find the declaration in the namespace scope since its name is bound in that scope. From this perspective,#2hand#1refers to the same entity. However, the issue is that [dcl.type.elab] p4 saysAny unqualified lookup for the identifier (in the first case) does not consider scopes that contain the target scope;
So, what is the target scope of the
Xat#1? Clang accepts this example while GCC rejects it.According to the behavior of GCC, it seems to consider the target scope of the
Xat#2as the innermost block scope such that#2and#1do not refer to the same entity. This result depends on that [dcl.type.elab] p3 applies to#2, that is, unqualified name lookup for the nameXat#2finds nothing, and p3 specifies the target scope for the elaborated-type-specifier, which is established on the target scope ofXat#2is the innermost block scope such that the namespace scope is not considered here, as per p4.As pointed out in the subject, this makes a circular definition. In short, for the analysis of the name
Xat#2, p4 depends on the target scope has been determined such that the containing scope is not considered for the result of the search of the unqualified name, however, the target scope, in turn, depends on the application of p3, which in turn, depends on the application of p4 that prevents consideration from the namespace scope in this case.The current wording is ok for the behavior of Clang.
This is probably just a clang bug.
I'm not seeing a circularity in the definition, sorry.
The target scope of E is the nearest enclosing namespace or block scope.
This is a definition depending on where E appears lexically, and this definition is independent on any lookup results or similar (you can and should ignore the "If" sentence in front). Thus, it's fine for p4 to limit the lookup to the target scope, and then the logic goes back to p3 to introduce the name as a class-name.
Maybe we could do this slight adjustment, in case it helps:
The target scope of E is
theits nearest enclosing namespace or block scope.@opensdh, any opinion here?
This is a definition depending on where E appears lexically, and this definition is independent on any lookup results or similar (you can and should ignore the "If" sentence in front).
Consider this example
struct X{ int a; }; void fun(){ struct X* ptr; // #1 ptr->a = 0; // #2 // we can also test static_assert(std::is_same_v<X, ::X>); }
This example is accepted by implementations. If we say the target scope of the elaborated-type-specifier at
#1is the block scope regardless of the result of the unqualified name lookup, that is to say, the classXat#1and#2is not the same entity as that of theXdefined in the namespace scope. However, they are the same entity according to the behavior of implementations.
The actual should be interpreted as: Since the unqualified name lookup for
Xat#1find the class definition in the namespace scope, hence "The target scope of E is the nearest enclosing namespace or block scope." does not apply to this case, hence [dcl.type.elab] p5 applies to this caseIf the identifier or simple-template-id resolves to a class-name or enum-name, the elaborated-type-specifier introduces it into the declaration the same way a simple-type-specifier introduces its type-name ([dcl.type.simple]).
Presumably, "resolve" means the name lookup finds the name. Otherwise, we cannot explain this example if the target scope is only determined by the place where the declaration lexically appears. The declaration lexically appears in block scope and the nearest such enclosing scope is the block scope. That means the
Xin the namespace scope and theXin the block scope are different entities since they have different target scopes.There's a misunderstanding here that the target scope always matters. In the
void fun() {struct X* ptr; …}example, lookup forXdoes find something (because there is nofriendto limit it), so thatstruct Xis simply no declaration at all, and it needs no target scope.This makes it sound like the reported circularity exists, since the "The target scope of E…" is then relevant only if the previous condition holds. However, we can still interpret it unambiguously because there is no other possibility for the target scope: any interpretation that relies on /4 allowing a distant lookup result, like that attributed to Clang in the
friend class X;example, is mistaken because there is no consistent reading where some other, more distant target scope is selected.What /3 and /4 are trying to say, and a direction in which we perhaps should edit them, is
The frobozz scope of E is the nearest enclosing namespace or block scope. […] The target scope of E is its frobozz scope.
[…] does not consider scopes that contain the frobozz scope.
although (the latter half of!) the suggested resolution would accomplish the same thing, trading a defined term for a bit of redundancy.
Reacted by XMH- changed the title
[-][dcl.type.elab] p3 The nominated E is not clear in the rule such that implementations have a divergence[/-][+]CWG2634 [dcl.type.elab] p3 The nominated E is not clear in the rule such that implementations have a divergence[/+]on Sep 19, 2022 Making the rule "the target scope of E is the nearest enclosing namespace or block scope" immediately follow the case it applies is also helpful.
If E contains an identifier but no nested-name-specifier and (unqualified) lookup for the identifier finds nothing, E shall not be introduced by the enum keyword and declares the identifier as a class-name; the target scope of such E is the nearest enclosing namespace or block scope.
To avoid misreading that the target scope is defined for any
Ethe paragraph is talking about. Moreover, giving a note for the otherwise case(i.e the identifier is found) is helpful, which leads us to read [dcl.type.elab] p5 that is about that case.
Incidentally, for the otherwise case(i.e the identifier is found), does the "elaborated-type-specifier" itself is a declaration? Seems not. Is it necessary to emphasize this point in the clause?
@jensmaurer I think it should be
The target scope for #1 is
f's block scopeinstead of
The target scope for #2 is
f's block scope
Full name of submitter (unless configured in github; will be published with the issue): Jim X
[dcl.type.elab] p3 says
What does
Erefer to in the emphasized wording? In this branch, E can refer to the elaborated-type-specifier in the declaration such as friend class-key nested-name-specifier identifier or friend class-key identifier. In other words, the elaborated-type-specifier in a declaration where the elaborated-type-specifier is not the sole constituent. The difference is one has a nested-name-specifier and the other does not.Presumably, the intent refers to the
Ethat does not have a nested-name-specifier. The other thing is whether theEreferred to in the emphasized rule should satisfy the condition "(unqualified) lookup for the identifier finds nothing" or not? If the condition should be satisfied, then the target ofEis said to be the nearest enclosing namespace or block scope, this makes [dcl.type.elab] p4 have a circularly definitionBecause the definition of the target scope relies on whether the condition that the unqualified lookup for the identifier finds nothing is true, and the consideration of the scopes for the unqualified lookup conversely relies on the target scope.
Example A
See https://godbolt.org/z/TzWbjffxq
The intent should be that the type at
#1and the type at#2is not the same entity since their target scopes are different.Example B
See https://godbolt.org/z/WG78Goqj5
[dcl.type.elab] p3 did not specify the target scope of E
struct F.The implementations have a divergence treatment in such two examples. Neither of them has a consensus for the same example.
It seems that GCC is right on the first example but not the second while Clang is right on the second example but not the first.
Suggested resolution
change [dcl.type.elab] p3
change [dcl.type.elab] p4