Regarding friend function definition and namespace scopes
Asked Answered
C

1

8

I was reading this blog post section, and I tried to play around with the snippet that was provided.

namespace N {
// 2
class A {
friend void f(A) {} // 1
};
}

If I understood correctly, the definition in // 1 will inject the name f where // 2 is located. However it will only be available via argument-dependent lookup. Fine.

There is a sentence in the post that caught my attention:

7.3.1.2/3 Namespace member definitions [namespace.memdef]p3

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 template 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).

Notice that nowhere is it stated that the name introduced by a friend-declaration must have any particular relation to name of the class it is declared and/or defined in, or any particular relation to the class at all (for that matter).

From this, I thought the following snippet would have been valid:

namespace N {
struct A {
};

struct B {
  friend void f(A) {
}
};

int main() {
  N::A a;
  f(a);
}

But it's rejected by both GCC7 and Clang 4.

t.cpp:19:3: error: ‘f’ was not declared in this scope

The funny thing is that, when I try to call f with a N::B object, I get the following error:

t.cpp:12:6: error: could not convert ‘b’ from ‘N::B’ to ‘N::A’

So here's my question:

Shouldn't f(A) be detected via ADL? Since both classes are in the namespace I don't see why this fails. I looked in the standard the section about friends, but failed to find a relevant section.

I wonder in which scope f(A) was injected, since GCC is able to find it when I try to give the wrong argument type via calling f(B).

Contortion answered 6/9, 2017 at 11:15 Comment(0)
T
3

From cppreference/cpp/language/friend:

A name first declared in a friend declaration within class or class template X becomes a member of the innermost enclosing namespace of X, but is not accessible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided - see namespaces for details.


From cppreference/cpp/language/namespace:

Names introduced by friend declarations within a non-local class X become members of the innermost enclosing namespace of X, but they do not become visible to lookup (neither unqualified nor qualified) unless a matching declaration is provided at namespace scope, either before or after the class definition. Such name may be found through ADL which considers both namespaces and classes.


This is consistent with your example - f takes an A, which is not the same type as the enclosing class.

If you change your example to...

namespace N {
struct A {
};

struct B {
  friend void f(B) {
}
};

int main() {
  N::B b;
  f(b);
}

...it will compile.


Related standard quote:

$14.3 [class.friend]

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 function can be defined in a friend declaration of a class if and only if the class is a non-local class ([class.local]), the function name is unqualified, and the function has namespace scope. [...] Such a function is implicitly an inline function. A friend function defined in a class is in the (lexical) scope of the class in which it is defined. A friend function defined outside the class is not ([basic.lookup.unqual]).

Tem answered 6/9, 2017 at 12:26 Comment(3)
That makes sense, but there's still one thing that is unclear to me. Isn't the innermost enclosing namespace of struct A the namespace N? Or does the compiler generate a hidden namespace? That would explain why the ADL doesn't work in my example. Or it might be because of a special case in ADL rules?Contortion
@Dante: "but is not accessible for lookup (except argument-dependent lookup that considers X)" - X in this case is A. You're trying to use ADL through B.Tem
Thanks a lot, that led me to [basic.lookup.argdep]§4.2 and the following sentence: Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.3) If I understood correctly, the name is injected in the enclosing namespace after the compiler checks that there is a friend declaration in the argument type class. Which makes sense.Contortion

© 2022 - 2024 — McMap. All rights reserved.