Skip to content

CWG3109 [class.protected] Update to cover splices #784

Description

@katzdm

Full name of submitter: Dan Katz

Reference (section label): [class.protected]/1

Issue description
[class.protected] establishes the "additional rule" for access to non-static data members in the context of forming a pointer-to-member. Just as [class.access.base]/5.3 waives access rules for members designated through a splice-expression, this clause should do the same. As the rules currently stand, the following is well-formed when m is a private member of Base:

class Base {
private:
  int m;
public:
  static constexpr auto rm = ^^m;
};
class Derived { static constexpr auto mp = &[:Base::rm:]; };

but [class.protected]/1 implies that the same is ill-formed when m is a protected member of Base, since there is no nested-name-specifier that denotes Derived or a class derived from Derived. Furthermore, the phrase "naming class" is used, whereas P2996 replaced this term with "designating class". Lastly, because a nested-name-specifier can be a splice-expression (e.g., &[:reflection_of_class:]::member), the verb "designates" should be preferred in lieu of "denotes".

Worth noting: This paragraph fails to account for access that occur in unevaluated contexts, but addressing this seems to tread too closely to CWG2902 for comfort, so I'm leaving that as-is for now (but added a comment on the relevant issue).

Suggested resolution
Apply the following changes to [class.protected]/1:

An additional access check beyond those described earlier in [class.access] is applied when a non-static data member or non-static member function is a protected member of its naming designating class ([class.access.base]).98 As described earlier, access to a protected member is granted because the reference occurs in a friend or direct member of some class C. If the access is to form a pointer to member ([expr.unary.op]), that member shall either be designated with a splice-expression or with a qualified name whose the nested-name-specifier shall denote designates C or a class derived from C. All other accesses involve a (possibly implicit) object expression ([expr.ref]). In this case, the class of the object expression shall be C or a class derived from C.

Optionally, we could try to drive-by fix the "unevaluated context issue" by modifying the last two sentences as follows:

All other potentially evaluated accesses involve a (possibly implicit) object expression ([expr.ref]). In this case, the class of the object expression shall be C or a class derived from C.

But if the interaction to CWG2902 is too close, then we can ignore it for now.

Activity

  1. changed the title [-][section.label] Update [class.protected] to cover splices[/-] [+][class.protected] Update to cover splices[/+] on Oct 25, 2025
  2. t3nsor commented on Oct 25, 2025

    @t3nsor

    If the member is designated by a splice-expression, shouldn't we skip the additional check in [class.protected] entirely? That's effectively how it works with private members.

  3. katzdm commented on Oct 25, 2025

    @katzdm
    Author

    If the member is designated by a splice-expression, shouldn't we skip the additional check in [class.protected] entirely? That's effectively how it works with private members.

    Yes, but we achieve that behavior by explicitly sanctioning the access in in [class.access.base]/5.3. I'm seeking to achieve the same here with:

    that member shall either be designated with a splice-expression or [...]

    What do you think? Does my suggested wording achieve that? Do you think there's a simpler way of expressing it?

  4. t3nsor commented on Oct 25, 2025

    @t3nsor

    You put the wording "that member shall either be designated with a splice-expression" in the part that talks about forming a pointer-to-member. It seems that the exemption should also apply to direct accesses (i.e. using .). We can say something like this:

    An additional access check beyond those described earlier in [class.access] is applied when a non-static data member or non-static member function is a protected member of its namingdesignating class ([class.access.base]) and is not designated by a splice-expression.

  5. katzdm commented on Oct 25, 2025

    @katzdm
    Author

    That direction works for me as well!

  6. katzdm commented on Oct 25, 2025

    @katzdm
    Author

    Revised suggestion:

    An additional access check beyond those described earlier in [class.access] is applied when a non-static data member or non-static member function is a protected member of its naming designating class ([class.access.base]) and is not designated by a splice-expression.98 As described earlier, access to a protected member is granted because the reference occurs in a friend or direct member of some class C. If the access is to form a pointer to member ([expr.unary.op]), the nested-name-specifier shall denote designate C or a class derived from C. All other potentially evaluated accesses involve a (possibly implicit) object expression ([expr.ref]). In this case, the class of the object expression shall be C or a class derived from C.

  7. jensmaurer commented on Oct 26, 2025

    @jensmaurer
    Member

    What's the addition of "potentially evaluated" doing? I think we want the special "protected" rules to apply even for unevaluated stuff.

  8. t3nsor commented on Oct 26, 2025

    @t3nsor

    I think the issue is that the current text fails to account for the fact that you can have something like sizeof(Base::m). It doesn't fall under "if the access is to form a pointer to member" nor does it involve an object expression because the implicit transformation doesn't happen in this case.

  9. jensmaurer commented on Nov 2, 2025

    @jensmaurer
    Member

    ... and we don't, in fact, apply the extra check for unevaluated stuff; is that the gist?

  10. jensmaurer commented on Nov 2, 2025

    @jensmaurer
    Member
  11. changed the title [-][class.protected] Update to cover splices[/-] [+]CWG3109 [class.protected] Update to cover splices[/+] on Nov 2, 2025
  12. t3nsor commented on Nov 2, 2025

    @t3nsor

    ... and we don't, in fact, apply the extra check for unevaluated stuff; is that the gist?

    Yes. It seems that the way implementations behave is that for unevaluated cases, the additional check applies only if you have a class member access (taking into account the fact that one is not formed implicitly).

    class A {
      protected:
        int m;
    };
    
    class B : A {
        void f() {
            auto p = &A::m;  // error
            auto s1 = sizeof(A::m);  // ok
            auto s2 = sizeof(A().m);  // error
        }
    };
    

    So we should say something like this: "Otherwise, if the access involves a (possibly implicit) object expression, [...]. [Note: Some unevaluated accesses do not fall under any of the rules in this paragraph and therefore, no additional check applies to them.]"

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