12

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.

Belloc
  • 6,318
  • 3
  • 22
  • 52
  • 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 stands with Russia Jul 10 '15 at 19:51
  • In the latter case that "outside" does not eliminate `struct Outer` from lookup. – AnT stands with Russia Jul 10 '15 at 19:53
  • 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 Jul 10 '15 at 19:54
  • 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 stands with Russia Jul 10 '15 at 20:01
  • 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 Jul 10 '15 at 20:16
  • Well, I'm not arguing with you. I'm agreeing acutlaly. Yes, I believe the lookup should have found `Outer::f` and it is `Outer::f` that should have been befriended, at least within one interpretation of the quoted text. And if that interpretation is incorrect. I'd like to know which word am I misunderstanding here. Is it the above "outside"? Or is it something else? – AnT stands with Russia Jul 10 '15 at 20:28

2 Answers2

9

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; }
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • According to [namespace.memdef]/3 that is not necessary. A lookup for the name `f` started in the friend declaration finds the name Outer::f. – Belloc Jul 10 '15 at 19:22
  • @Belloc, the example code presented in that sections has `namespace A` as the enclosing `namespace`. – R Sahu Jul 10 '15 at 19:27
  • I don't see the OP making that assumption anywhere. Which part of the standard text says that only the namespace scopes should be considered, and not class scopes? – AnT stands with Russia Jul 10 '15 at 19:55
  • Take for example this snippet: `struct Outer { class C{}; class Inner { friend class C; static const int i = 0; }; }; class C{ static const int i = Outer::Inner::i; };`. The 3 compilers clang, gcc and vs2013 all befriend the class `Outer::C`, instead of `::C`. So the lookup started in the friend declaration, for the name `C`, finds the name `Outer::C`. Why wouldn't it find the name `f`? – Belloc Jul 10 '15 at 20:08
  • @AnT, I have highlighted a portion of the standard that the OP has posted, which clearly uses `namespace`, not `class`. – R Sahu Jul 10 '15 at 20:47
  • @RSahu But that's **if** it's first declared. *Is* `f` first-declared in the friend declaration? Is `C`? – Barry Jul 10 '15 at 20:55
  • @R Sahu: Apparently, you missed the key point the OP made. The passage you quoted (and highlighted) only applies to situations when the name in question is **first** declared by the member declaration. But the debate that we are having here is specifically about whether the friend declaration of `void f()` is indeed the *first* one. The further text in the standard is dedicated to clarifying this matter: how to tell *first* declaration from *non-first* one. – AnT stands with Russia Jul 10 '15 at 20:59
  • And the discussion here is focused on that text, since it seems to be ambiguous. You made you quote under assumption that this is a *first* declaration. But neither I nor the OP subscribes to that assumption. Which is why your quote with your highlight doesn't do anything for me. – AnT stands with Russia Jul 10 '15 at 20:59
  • @RSahu You said your last two snippets are equivalent? That's not true. The first one (the third shown above) doesn't compile because the friend declaration befriends `Outer::C`, and **not** `::C`, while the second (the fourth shown above) compiles because the friend declaration befriends `::C`. In other words, the two snippets are not equivalent! – Belloc Jul 12 '15 at 14:44
  • @Belloc, You are right about that. It looks like the logic for looking up for function names and class names used by a friend declaration are different. – R Sahu Jul 12 '15 at 16:16
2

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 2
    I'm not sure what you are trying to say by emphasizing the "...shall not consider any scopes outside the innermost enclosing namespace". The term "outside" refers to further "larger" namespaces "wrapping" the innermost enclosing one (if any). The scope of `struct Outer` the OP believes should be considered is *not outside* the innermost enclosing namespace. I.e. the emphasized passage does not seem to change anything relevant to the OP's question. – AnT stands with Russia Jul 10 '15 at 19:45
  • @AnT @AnT It exactly answers OP's question. Outside of can also mean exclusive of. The singular scope considered is the innermost enclosing namespace. Which is `::`, and not `::Outer`. – Barry Jul 10 '15 at 19:55
  • It only "exactly answers OP's question" under that assumption about the intended meaning of "outside". And that's the issue. What does "outside" mean in this case? Are scopes nested *inside* the innermost namespace scope also considered "outside" in the intended meaning of the term? – AnT stands with Russia Jul 10 '15 at 20:03
  • And where do you see the potential for inconsistency here? It looks virtually the same to me. What you quoted simply says that while doing the lookup we should not venture farther away (farther outside) than the innermost enclosing non-class scope. But it does not eliminate any class scopes from lookup. I.e. speaking informally, such passages define the *radius* of the search, but they don't seem to punch any holes in the search region. – AnT stands with Russia Jul 10 '15 at 20:12
  • @Barry Take for example this snippet: `struct Outer { class C{}; class Inner { friend class C; static const int i = 0; }; }; class C{ static const int i = Outer::Inner::i; };`. The 3 compilers clang, gcc and vs2013 all befriend the class `Outer::C`, instead of `::C`. So the lookup started in the friend declaration, for the name `C`, finds the name `Outer::C`. Why wouldn't it find the name `f`? – Belloc Jul 10 '15 at 20:18
  • @AnT I agree this wording is ambiguous. As far as I can tell, the original rationale for this rule suggests the rule shall restrict all friend declarations to *one* scope, which excludes class members. See http://stackoverflow.com/q/31005610/ – dyp Jul 10 '15 at 20:27
  • @dyp It *does* say "... look **until** the nearest enclosing namespace is reached." I'm going to rewrite this answer. – Barry Jul 10 '15 at 20:33
  • @Belloc You convinced me. – Barry Jul 10 '15 at 20:39
  • @AnT Curious what the gcc/llvm guys end up saying - I linked back to here. Definitely something needs to be worded better somewhere. – Barry Jul 10 '15 at 21:21
  • @Barry I definitely agree with this last comment of yours. See this discussion started by me in std-discussion google groups: https://groups.google.com/a/isocpp.org/d/msg/std-discussion/o_kOIKHgSVg/MX8oO0-q9A0J – Belloc Jul 10 '15 at 23:43
  • @Barry Regarding your example given in your answer above, I have one more thing to say: according to Richard Smith your code is ill-formed. You have to consider §3.3.7/1 (2). Although, if you read this item (2), in my opinion, it's practically impossible to reach this conclusion, i.e., that the code is ill-formed. To better understand what this is all about, you should read the discussion started at this point, https://groups.google.com/a/isocpp.org/d/msg/std-discussion/o_kOIKHgSVg/yTZJF64hqhoJ, within the discussion mentioned in my prior post. – Belloc Jul 10 '15 at 23:56
  • @Barry Well, even if it does say "until", that would violate the more general rule (or aim) that *"All friend declarations for a given name must declare entities in one particular scope."* ... – dyp Jul 11 '15 at 06:33
  • @Belloc As far as I can tell, the remark about §3.3.7/1 applies to your first example in this message: https://groups.google.com/a/isocpp.org/d/msg/std-discussion/o_kOIKHgSVg/VtjOxDd3jksJ but not to the second. The name `Outer::C` must be introduced before the friend declaration, otherwise §3.3.7/1 is violated. Barry's example is more like your second example, which AFAICT do not violate this paragraph. – dyp Jul 11 '15 at 06:41
  • @dyp It applies to both examples. My interpretation of §3.3.7/1 (2) is that the behavior of a class should not depend on the order of its members and/or declarations. For instance, in the second example that you mentioned, the friend declaration `friend class C;` inside Inner befriends `class C` defined in `Outer`. But if you move this declaration after `class Inner` the friend declaration will befriend `::C` and this is what §3.3.7/1 (2) says it's not allowed, according to my interpretation of what David Rodriguez and Richard Smith said on the above link. – Belloc Jul 11 '15 at 12:05
  • @Belloc I don't think this can be the intention. Otherwise, even something like `struct X{}; struct Y { struct X {}; auto foo() -> X; };` were illegal. I rather think Richard Smith's formulation https://groups.google.com/a/isocpp.org/d/msg/std-discussion/o_kOIKHgSVg/AQjERK8oFrwJ shows the intent of the rule much better than the actual text. If you have access to D&E, the whole section 6.3.1 provides further insight. – dyp Jul 11 '15 at 12:25
  • @dyp I believe this code is illegal according to §3.3.7/1 (2). See the examples in the paragraph. – Belloc Jul 11 '15 at 13:45
  • @Belloc The examples in 3.3.7 all introduce a member after using a name that could (it it were used after the declaration) refer to that member. This is not the case in your second example here: https://groups.google.com/a/isocpp.org/forum/#!msg/std-discussion/o_kOIKHgSVg/VtjOxDd3jksJ – dyp Jul 11 '15 at 14:16
  • @dyp See [this answer](http://stackoverflow.com/a/23773620/1042389) by Jerry Coffin. This is exactly what I'm saying. – Belloc Jul 11 '15 at 19:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/83046/discussion-between-dyp-and-belloc). – dyp Jul 11 '15 at 19:55