14

clang++, g++, and MSVC disagree on this code:

class A {
private:
    enum class E { NO, YES };
    class B {
    private:
        friend E f1() { return E::YES; }
        // friend E f2();
    };
};

// A::E f2() { return A::E::YES; }

int main() {}

clang++ accepts the code as shown. g++ and MSVC complain in f1 that A::E is inaccessible. If function f2 is uncommented, all three compilers complain at its definition that A::E is inaccessible.

Is f1 in fact valid?

The relevant Standard pieces I found are:

[class.access.nest]:

A nested class is a member and as such has the same access rights as any other member.

Though this alone doesn't mean friends of the nested class have all the same rights as the nested class.

[class.access.base]/5:

The access to a member is affected by the class in which the member is named. This naming class is the class in which the member name was looked up and found. A member m is accessible at the point R when named in class N if

  • m as a member of N is public, or

  • m as a member of N is private, and R occurs in a member or friend of class N, or

  • m as a member of N is protected, and..., or

  • there exists a base class B of N that is accessible at R, and m is accessible at R when named in class B.

So f2 is invalid, because there the naming class for A::E is definitely A, there are no base classes involved, and the definition of f2 is not a member or friend of A and does not "occur in" a member or friend of A.

In f1 the naming class for unqualified E is also A. ([basic.lookup.unqual] says a lookup for the name E in class A::B is done first, but it's not "found" there, so then a lookup in class A is done, and the member is found.) But I guess the big question then is, does the definition of f1 "occur in" a member of A? That member, if so, would have to be class A::B.

Community
  • 1
  • 1
aschepler
  • 70,891
  • 9
  • 107
  • 161
  • @Barry I think the portion beginning with "the access to a member" applies to all uses of a member name, with "named in class `N`" referring back to the definition of "naming class", not implying that _R_ must be in a class scope. Some other text somewhat describes the meaning of access levels with respect to members, but not with nearly this amount of precision. If it were meant to only apply to uses "in a class", it would be pointless to specify "occurs in a member" and not helpful to specify "occurs in a friend". – aschepler Apr 04 '19 at 20:28
  • ... and actually the Standard Example in the same paragraph discusses only a member name in a friend function defined outside the befriending class. – aschepler Apr 04 '19 at 20:31
  • 1
    Oh, yeah, you're right. I misunderstood what those words meant. – Barry Apr 04 '19 at 21:45

1 Answers1

7

I think gcc and msvc are right.

From [class.friend]/1, emphasis mine:

A friend of a class is a function or class that is given permission to use the private and protected member names from the class. A class specifies its friends, if any, by way of friend declarations. Such declarations give special access rights to the friends, but they do not make the nominated friends members of the befriending class.

When you have friend E f1(), that gives f1 permission to use the private and protected names of B, specifically. E is not a private or protected name of B, it's a name that B simply has access too.


This is conceptually similar to [class.friend]/10:

Friendship is neither inherited nor transitive.

Since it would imply that the rule is that f1 can access B's stuff, and B can access A's stuff, therefore f1 can access A's stuff.

Barry
  • 286,269
  • 29
  • 621
  • 977