Repository navigation
CWG2634 [dcl.type.elab] p3 The nominated E is not clear in the rule such that implementations have a divergence #80
Description
Full name of submitter (unless configured in github; will be published with the issue): Jim X
[dcl.type.elab] p3 says
Otherwise, an elaborated-type-specifier E shall not have an attribute-specifier-seq. 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 E is the nearest enclosing namespace or block scope.
What does E refer 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 E that does not have a nested-name-specifier. The other thing is whether the E referred 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 of E is said to be the nearest enclosing namespace or block scope, this makes [dcl.type.elab] p4 have a circularly definition
Any unqualified lookup for the identifier (in the first case) does not consider scopes that contain the target scope;
Because 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
#include <tuple>
auto fun(){
struct C{ // #1
void show();
int c;
};
{
class U{
friend struct C; // #2 // target scope is the innermost block scope that contains `U`?
int u;
};
return std::tuple<C,U>{};
}
}
using Ret = decltype(fun());
using Calias = std::tuple_element<0,Ret>::type;
using Ualias = std::tuple_element<1,Ret>::type;
void Calias::show(){
c = 10;
Ualias b;
b.u = 0; // GCC rejects it but Clang accepts it.
}The intent should be that the type at #1 and the type at #2 is not the same entity since their target scopes are different.
Example B
See https://godbolt.org/z/WG78Goqj5
namespace X{
namespace C{
struct F;
}
using namespace C;
class A{
friend struct F; // what is the target scope? it's not specified in the current wording
int c;
};
struct C::F{
void fun(){
A obj;
obj.c = 0; // Clang accepts it but GCC rejects it
}
};
}[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
Otherwise, an elaborated-type-specifier E shall not have an attribute-specifier-seq. 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
Eis the nearest enclosing namespace or block scope. Otherwise, the target scope ofEis the scope that is the target scope of the declarations found by the lookup for the terminal name ofE.
change [dcl.type.elab] p4
Any unqualified lookup for the identifier (in the first case) does not consider scopes that contain the nearest enclosing namespace or block scope; no name is bound.
Activity
[-][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[/+]xmh0511 commentedon Sep 7, 2022
A simplified example is more helpful to expose the issue here.
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 saysSo, 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.
jensmaurer commentedon Sep 7, 2022
This is probably just a clang bug.
I'm not seeing a circularity in the definition, sorry.
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:
@opensdh, any opinion here?
xmh0511 commentedon Sep 7, 2022
Consider this example
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 casePresumably, "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.opensdh commentedon Sep 7, 2022
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
although (the latter half of!) the suggested resolution would accomplish the same thing, trading a defined term for a bit of redundancy.
[-][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[/+]jensmaurer commentedon Sep 19, 2022
CWG2634
xmh0511 commentedon Sep 20, 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.
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?
languagelawyer commentedon Aug 23, 2023
@jensmaurer I think it should be
instead of