Reputation: 13424
Suppose we have a class foo
from a namespace space
which declares a friend
function named bar
, which is later on defined, like so:
namespace space {
struct foo {
friend void bar(foo);
};
}
namespace space {
void bar(foo f) { std::cout << "friend from a namespace\n"; }
}
To my understanding, friend void bar(foo);
declares bar
to be a free function inside space
taking a foo
by value. To use it, we can simply do:
auto f = space::foo();
bar(f);
My understanding is that we don't have to say space::bar
, because ADL will see that bar
is defined in the same namespace
as foo
(its argument) and allow us to omit the full name qualification. Nonetheless, we are permitted to qualify it:
auto f = space::foo();
space::bar(f);
which works (and should work) exactly the same.
Things started to get weird when I introduced other files. Suppose that we move the class and the declaration to foo.hpp
:
#ifndef PROJECT_FOO_HPP
#define PROJECT_FOO_HPP
namespace space {
struct foo {
friend void bar(foo);
};
}
#endif //PROJECT_FOO_HPP
and the definitions to foo.cpp
:
#include "foo.hpp"
#include <iostream>
namespace space {
void bar(foo f) { std::cout << "friend from a namespace\n"; }
}
notice that all I did was I moved (didn't change any code) stuff to a .hpp
-.cpp
pair.
What happened then? Well, assuming that we #include "foo.hpp"
, we still can do:
auto f = space::foo();
bar(f);
but, we are no longer able to do:
auto f = space::foo();
space::bar(f);
This fails saying that: error: 'bar' is not a member of 'space'
, which is, well, confusing. I am fairly certain that bar
is a member of space
, unless I misunderstood something heavily. What's also interesting is the fact that if we additionally declare (again!) bar
, but outside of foo
, it works. What I mean by that is if we change foo.hpp
to this:
#ifndef PROJECT_FOO_HPP
#define PROJECT_FOO_HPP
namespace space {
struct foo {
friend void bar(foo);
};
void bar(foo); // the only change!
}
#endif //PROJECT_FOO_HPP
it now works.
Is there something with header / implementation files that alters the expected (at least for me) behaviour? Why is that? Is this a bug? I am using gcc version 10.2.0 (Rev9, Built by MSYS2 project).
Upvotes: 6
Views: 695
Reputation: 5565
There's a slight subtlety that the friend
declaration, while it doesn't require a previous declaration of the function or class your class is befriending, does not make the function visible for lookup except via ADL.
A name first declared in a friend declaration within a class or class template X becomes a member of the innermost enclosing namespace of X, but is not visible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided - see namespaces for details.
This is why you're able to find bar(f)
(performs ADL) but not space::bar(f)
(fully qualifying the name means ADL is not invoked).
Calling code that doesn't find bar
via ADL needs to see a declaration. In the version where everything is in one file, calling code will see the entire definition of space::foo
. When you split it into an HPP and a CPP file, calling code only sees the friend declaration which provides limited accessibility.
As you identified, if you want to make the function visible via ordinary lookup, put a declaration of foo
in "foo.hpp":
#ifndef PROJECT_FOO_HPP
#define PROJECT_FOO_HPP
namespace space {
struct foo {
friend void bar(foo);
};
void bar(foo); // Now code that includes foo.hpp will see a declaration for bar
}
#endif //PROJECT_FOO_HPP
Upvotes: 4