12
votes

According to clang, gcc and vs2013, the function Outer::f is not a friend of the class Outer::Inner.

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

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

From [namespace.memdef]/3 I would expect the function Outer::f to be a friend of Outer::Inner, instead of ::f, because the friend declaration is not the first in its namespace containing the name f.

[namespace,memdef]/3 (emphasis is mine):

Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class, function, class template or function template97 the friend is a member of the innermost enclosing namespace. The friend declaration does not by itself make the name visible to unqualified lookup (3.4.1) or qualified lookup (3.4.3). [ Note: The name of the friend will be visible in its namespace if a matching declaration is provided at namespace scope (either before or after the class definition granting friendship). — end note ] If a friend function or function template is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2). If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace.

2
The quoted text says "...the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace". The question here is what is "outside" supposed mean? Is it supposed to synonymous with "except" or "besides" (in which case the above passage means that the lookup is performed in the innermost namespace and nowhere else)? But it this case, if I'm not mistaken, it should say "outside of"... Or is "outside" supposed to refer to larger enveloping namespaces that surround the innermost one?AnT
In the latter case that "outside" does not eliminate struct Outer from lookup.AnT
I presume the lookup is performed in the innermost namespace and nowhere else, for the cases explicited in the sentence: If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace., where the innermost enclosing namespace, in this case, is the global namespace.Belloc
Why did you jump to that conclusion? I don't see any definitive support for it in the standard text. The only thing that passage says is what is illustrated in the example in [namespace.memdef]/3 by friend void h(int); declaration. It says that A::h is a friend, ::h not considered. The quoted passage explains why ::h is not considered: because ::h is declared in :: scope outside of A scope. In your example struct Outer scope is not outside of :: scope.AnT
For me it's very clear. The innermost namespace in the example in the Standard is the namespace A, while in the example above, the innermost namespace is the global namespace. As you said, the Outer scope is not outside of the global scope. Therefore the lookup started in the friend declaration finds the name Outer::f which then should be the befriended function, and not ::f.Belloc

2 Answers

9
votes

The first part of the standard that you quoted says (emphasis mine):

Every name first declared in a namespace is a member of that namespace. If a friend declaration in a nonlocal class first declares a class or function the friend class or function is a member of the innermost enclosing namespace.

You are assuming a class is the same as a namespace, which is not correct.

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

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

should work. To use the class member function as the friend, you'll have to use:

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

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

According to [namespace.memdef]:

If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace.

What does "outside" mean? It could mean (1) external of (as in, all scopes within the innermost enclosing namespace are permitted, but no others) or it could mean (2) exclusive of (as in, only the innermost enclosing namespace is considered). The wording is potentially ambiguous. However, consider this example which is merged from OP's original question and OP's comments:

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() { }

Based on wording (1), Outer::f and Outer::C should be friends of Inner. Based on wording (2), ::f and ::C should be the friends. One or the other interpretation could make sense, however both GCC and Clang end up with ::f and Outer::C as the friends, which clearly doesn't make any sense. I have filed GCC Bug 66836 and Clang Bug 24088. So either both compilers are wrong in one direction or another, or there's some part of the standard that explains this logic that definitely escapes me. I wouldn't bet against the latter.