In an unprecedented feat of miraculousness, Freenode's ##C++ discussion channel saw — in the space of no more than twenty minutes — the same question asked twice of one of C++'s more subtle features. Just how do you declare a
friend from the global namespace?
To demonstrate the scenario, consider the following code:
Although the member variable
N::S::x is marked private,
operator<< function should allow access to it; therefore, the expected output is "1". But not so!
The reason is almost disappointingly inelaborate.
friend statements are, in fact, function declarations too.
That means that when you write
friend ostream& operator<<(ostream& os, const S&) you're saying "there's a function with this signature, and it's allowed to access stuff in me". The problem here is that the function declaration is inside the namespace
N, so you've managed to implicitly declare a function
ostream& N::operator<<(ostream& os, const S&)… but your actual
operator<< is in the global namespace. You've declared a function that you never define nor use.
In itself this is harmless, but it does mean that your
friend statement has nothing to do with the global
operator<< that you actually use. Consequently, access to private or protected members from within it is still prohibited.
The most obvious fix here is to explicitly specify that you're declaring and
friending a function in the global namespace, using the "
::" operator (line 6):
(I've also added a couple of now-necessary forward declarations.)
This is all well and good, but let's take a look at what happens when we be
friend a different function. I've picked a simple one called
F that takes a ref-to-const-
S and returns an
All that's really changed is that the function has a different signature.
The key difference here is that our
ostream&, whereas our
int. One is a reference, and one is not. And the problem relates to parsing.
Let's take a look at the two
friend statements side by side:
In the first example, it's clear to the compiler what you are doing.
ostream& can only be a type (due to the placement of the
& symbol, and a few other things) and the rest falls into place.
But in the second example things aren't quite so clear. The compiler doesn't care about spaces quite as much as you or I, and in fact the code would mean the same in all the following variations:
int::F could be seen as a function
F inside a namespace
int is a keyword and this would be quite illegal. The compiler knows this, so the example compiles successfully and means what you expect.
But what if we didn't use
int? Still returning by value, let's use a return type that isn't a keyword:
The compiler now sees a declaration and befriending of a function
S::F that returns nothing, which is illegal. Parsing fails. And although we've tweaked around with the code a couple of times, we've ended up with something otherwise completely valid: this code structure is hardly unusual.
The solution to rule them all
Fortunately, we can rely on our trusty parentheses to put the topic to rest once and for all. Check out the new
It may seem like I've been describing an edge case, but this really does come up more often than one might think. Hopefully this article can serve as some kind of reference for when that day comes.