Имеет ли друг в классе вложенного класса доступ к внешним членам класса?

clang++, g++ и MSVC не согласны с этим кодом:

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++ принимает код, как показано. g++ и MSVC жалуются в f1 что A::E недоступен. Если функция f2 не закомментирована, все три компилятора при своем определении жалуются, что A::E недоступен.

Действительно ли f1 действителен?

Соответствующие Стандартные части, которые я нашел:

[class.access.nest]:

Вложенный класс является членом, и поэтому имеет те же права доступа, что и любой другой член.

Хотя это само по себе не означает, что друзья вложенного класса имеют все те же права, что и вложенный класс.

[class.access.base]/5:

Доступ к члену зависит от класса, в котором он назван. Этот класс именования является классом, в котором имя члена было найдено и найдено. Член m доступен в точке R при названии в классе N если

  • m как член N является публичным, или

  • m как член N является частным, а R встречается в члене или друге класса N, или

  • m как член N защищен, и..., или

  • существует базовый класс B из N который доступен в R, и m доступен в R, когда назван в классе B

Таким образом, f2 недопустим, потому что класс именования для A::E определенно равен A, базовые классы не задействованы, и определение f2 не является членом или другом A и не "встречается" в члене или друге. из A.

В f1 класс именования для неквалифицированного E также равен A ([basic.lookup.unqual] говорит, что поиск имени E в классе A::B выполняется первым, но он там не "найден", поэтому выполняется поиск в классе A, и член найден.) Но я полагаю, что главный вопрос в том, встречается ли определение f1 в члене A? Этот член, если так, должен быть class A::B

Ответ 1

Я думаю, что gcc и msvc правы.

От [class.friend]/1, акцент мой:

Друг класса - это функция или класс, которому дано разрешение использовать закрытые и защищенные имена членов класса. Класс указывает своих друзей, если таковые имеются, посредством объявлений друзей. Такие объявления дают особые права доступа друзьям, но они не делают назначенных друзей членами класса дружбы.

Когда у вас есть friend E f1(), это дает f1 разрешение использовать частные и защищенные имена B, в частности. E не является частным или защищенным именем B, это имя, к которому у B тоже есть доступ.


Концептуально это похоже на [class.friend]/10:

Дружба не является ни наследственной, ни переходной.

Поскольку это подразумевает, что правило состоит в том, что f1 может получить доступ к f1 B, а B может получить доступ к содержимому A, поэтому f1 может получить доступ к f1 A