Skip to content

CWG2634 [dcl.type.elab] p3 The nominated E is not clear in the rule such that implementations have a divergence #80

Description

@xmh0511

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 E is the nearest enclosing namespace or block scope. Otherwise, the target scope of E is the scope that is the target scope of the declarations found by the lookup for the terminal name of E.

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

  1. 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
  2. xmh0511 commented on Sep 7, 2022

    @xmh0511
    Author

    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 X at #1 founds nothing, hence the elaborated-type-specifier introduces class-name X and 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 #2 is 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, #2 hand #1 refers to the same entity. However, the issue is that [dcl.type.elab] p4 says

    Any 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 X at #1? Clang accepts this example while GCC rejects it.

    According to the behavior of GCC, it seems to consider the target scope of the X at #2 as the innermost block scope such that #2 and #1 do 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 name X at #2 finds nothing, and p3 specifies the target scope for the elaborated-type-specifier, which is established on the target scope of X at #2 is 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 X at #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.

  3. jensmaurer commented on Sep 7, 2022

    @jensmaurer
    Member

    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 the its nearest enclosing namespace or block scope.

    @opensdh, any opinion here?

  4. xmh0511 commented on Sep 7, 2022

    @xmh0511
    Author

    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 #1 is the block scope regardless of the result of the unqualified name lookup, that is to say, the class X at #1 and #2 is not the same entity as that of the X defined 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 X at #1 find 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 case

    If 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 X in the namespace scope and the X in the block scope are different entities since they have different target scopes.

  5. opensdh commented on Sep 7, 2022

    @opensdh

    There's a misunderstanding here that the target scope always matters. In the void fun() {struct X* ptr; …} example, lookup for X does find something (because there is no friend to limit it), so that struct X is 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.

  6. 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
  7. jensmaurer commented on Sep 19, 2022

    @jensmaurer
    Member
  8. xmh0511 commented on Sep 20, 2022

    @xmh0511
    Author

    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 E the 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?

  9. languagelawyer commented on Aug 23, 2023

    @languagelawyer

    @jensmaurer I think it should be

    The target scope for #1 is f's block scope

    instead of

    The target scope for #2 is f's block scope

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions