Функция-член Outer:: f() не является другом класса Outer:: Inner. Зачем?

Согласно clang, gcc и vs2013, функция Outer::f является не другом класса Outer::Inner.

struct Outer {
    void f() {}
    class Inner {
        friend void f();
        static const int i = 0;
    };
};

void f() { int i = Outer::Inner::i; }

Из [namespace.memdef]/3 я ожидал бы, что функция Outer::f будет другом Outer::Inner, а не ::f, потому что объявление друга не является первым в его пространство имен, содержащее имя f.

[namespace, memdef]/3 (акцент мой):

Каждое имя, впервые объявленное в пространстве имен, является членом этого Пространство имен. Если объявление друга в нелокальном классе сначалаобъявляет класс, функцию, шаблон или функцию класса template 97 друг является членом самого внутреннего охватывающее пространство имен. Декларация друга сама по себе не делает имя, видимое для неквалифицированного поиска (3.4.1) или квалифицированный поиск (3.4.3). [Примечание: имя друга будет видимым в его пространство имен, если в области пространства имен предоставляется соответствующее объявление (до или после определения класса, дающего дружбу). - end note] Если вызывается функция функции или шаблон функции, ее имя может быть найдено по имени, которое рассматривает функции из пространства имен и классы, связанные с типами функций аргументы (3.4.2). Если имя в объявлении друга не является или идентификатор шаблона, а декларация - это функция или разработанный тип-спецификатор, поиск, чтобы определить, является ли объект был ранее объявлен, не рассматривает какие-либо области вне внутреннее пространство имен.

Ответ 1

Первая часть стандарта, о которой вы указали, говорит (внимание мое):

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

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

namespace Outer {
    void f();
    class Inner {
        friend void f();
        static const int i = 0;
    };
}

void Outer::f() { int i = Outer::Inner::i; }

должен работать. Чтобы использовать функцию члена класса как друга, вам нужно будет использовать:

struct Outer {
    void f();
    class Inner {
        friend void Outer::f();
        static const int i = 0;
    };
};

void Outer::f() { int i = Outer::Inner::i; }

Ответ 2

В соответствии с [namespace.memdef]:

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

Что означает "снаружи"? Это может означать (1) внешний (как и в случае, все области внутри внутреннего пространства имен, разрешенные, но не другие), или это может означать (2) исключение (в том числе, рассматривается только самое внутреннее пространство имен). Формулировка потенциально неоднозначна. Однако рассмотрим этот пример, который объединен с исходным вопросом OP и комментариями OP:

struct Outer {
    void f() { }
    class C { void foo(); };

    class Inner {
        friend class C;
        friend void f();
        static const int i = 0;
    };
};

void f() { (void)Outer::Inner::i; }               // compiles on GCC,Clang
void Outer::C::foo() { (void)Outer::Inner::i; }   // compiles on GCC,Clang

int main() { }

В соответствии с формулировкой (1), Outer::f и Outer::C должны быть друзьями Inner. Основываясь на формулировке (2), ::f и ::C должны быть друзьями. Одна или другая интерпретация может иметь смысл, однако как GCC, так и Clang заканчиваются ::f и Outer::C как друзья, что явно не имеет никакого смысла. Я зарегистрировал GCC Bug 66836 и Clang Bug 24088. Поэтому либо оба компилятора ошибаются в том или ином направлении, либо там какая-то часть стандарта, которая объясняет эту логику, которая определенно ускользает от меня. Я бы не стал спорить с последним.