Friend functions and scoping

P

Paul Bibbings

In §11.5.1 of TC++PL (Special Edition, 2000), "Finding Friends,"
Stroustrup says:

"Like a member declaration, a friend declaration does not
introduce a name into an enclosing scope."

A few lines earlier in the introduction to the section (11.5,
"Friends") he'd explicitly compared friend function declarations to
member function declarations, saying:

"Like a member function, a friend function is explicitly declared
in the declaration of the class of which it is a friend. It is
therefore as much a part of that interface as is a member function."

This is confusing me a little when I find, through working examples,
that the contrary of his first statement 'appears' to be the case,
despite his overall emphasis on the similarity of the two cases. Thus,
to simplify his example to working code, consider:

#include <iostream>

namespace N {
class C {
char *_c;
public:
C(char *c): _c(c) { }
friend void f(C); // friend function
void g(); // member function
};

void (*p)(C) = &f; // [Stroustrup - error: no f()
in scope] compiles fine, with definition below
}

// adding the definitions ...
void N::f(C c) { // friend scope: N::
std::cout << c._c << '\n';
}

void N::C::g() { // member scope: N::C::
std::cout << "In g()..." << '\n';
}

int main() {
N::C c("Hello, World!");
f(c); // uses the type of
parameter c to locate f() in N::
c.g();

return 0;
}

As the definitions of member g() and friend f() show, whereas g is
scoped in this example to the class definition, that is to N::C::, f
is scoped to N::, which I would see as the "enclosing scope" of C. So
I'm a little confused by his "LIKE a member declaration, a friend
declaration does NOT introduce a name into an enclosing scope."
Rather, unlike the member declaration, the friend declaration seems to
being doing just that, unless I am not understanding his words
correctly.

Furthermore, the definition of the function pointer is supposed to
fail, there being "no f() in scope." Of course, without the definition
of f below this it does indeed fail at linking, but with the
definition of f in place as above, everything appears to be fine.

Very possibly I'm merely missing the intent of his meaning, but I'm
struggling to find a sense that better fits his words. In particular,
where the example he gives does not explicitly include namespacing,
thus putting f() in the global namespace, it's hard to see exactly
what "enclosing scope" it doesn't go in to.

Note: The above has compiled for me with gcc 3.4.4 (cygwin) and cl v.
15.00.30729.01 (VC++ Express), just in case this is a non-standard
compiler issue.

Any help with understanding this would be greatly appreciated.

Regards PB
 
S

Stefan Ram

Paul Bibbings said:
"Like a member function, a friend function is explicitly declared
in the declaration of the class of which it is a friend. It is
therefore as much a part of that interface as is a member function."

I am not sure, whether this is always true.

Here, »g« seems to be declared within the class specifier:

class K { friend void g(); };
void g() {}
int main() { g(); }

But when »g« is qualified, it does not seem to be declared
anymore:

class K { friend void ::g(); };
void g() {}
int main() { g(); }

"ComeauTest.c", line 1: error: the global scope has no "g"
class K { friend void ::g(); };
^

(gcc seems to accept this without an error message.)

One can remove the error by adding an additional declaration:

void g();
class K { friend void ::g(); };
void g() {}
int main() { g(); }
 
J

James Kanze

In §11.5.1 of TC++PL (Special Edition, 2000), "Finding Friends,"
Stroustrup says:
"Like a member declaration, a friend declaration does not
introduce a name into an enclosing scope."
A few lines earlier in the introduction to the section (11.5,
"Friends") he'd explicitly compared friend function
declarations to member function declarations, saying:
"Like a member function, a friend function is explicitly declared
in the declaration of the class of which it is a friend. It is
therefore as much a part of that interface as is a member function."
This is confusing me a little when I find, through working
examples, that the contrary of his first statement 'appears'
to be the case, despite his overall emphasis on the similarity
of the two cases.

Two possibilities behind your observations:

-- You're using an old compiler. In pre-standard C++, a friend
declaration injected the name into the surrounding scope, so
that it could be found. If this is the case, upgrade your
compiler.

-- You're confused by ADL. Although the friend name isn't
injected into the surrounding scope, friends will typically
have some argument which depends on the class, and will
cause the compiler to look into the class because of
argument dependent lookup.
Thus, to simplify his example to working code, consider:

#include <iostream>
namespace N {
class C {
char *_c;
public:
C(char *c): _c(c) { }
friend void f(C); // friend function
void g(); // member function
};
void (*p)(C) = &f; // [Stroustrup - error: no f()
in scope] compiles fine, with definition below

This should NOT compile. At this point in the code, there is no
name f which is visible.
// adding the definitions ...
void N::f(C c) { // friend scope: N::
std::cout << c._c << '\n';
}
void N::C::g() { // member scope: N::C::
std::cout << "In g()..." << '\n';
}
int main() {
N::C c("Hello, World!");
f(c); // uses the type of
parameter c to locate f() in N::
c.g();

As the definitions of member g() and friend f() show, whereas
g is scoped in this example to the class definition, that is
to N::C::, f is scoped to N::, which I would see as the
"enclosing scope" of C. So I'm a little confused by his "LIKE
a member declaration, a friend declaration does NOT introduce
a name into an enclosing scope." Rather, unlike the member
declaration, the friend declaration seems to being doing just
that, unless I am not understanding his words correctly.

It's difficult to explain clearly. The fully qualified name of
the function f, after the friend declaration, is ::N::f. The
function itself is in the surrounding namespace scope. But the
friend declaration does not introduce the name into this scope;
after the friend declaration, the name is still only visible in
the class scope ::N::C.
Furthermore, the definition of the function pointer is
supposed to fail, there being "no f() in scope." Of course,
without the definition of f below this it does indeed fail at
linking, but with the definition of f in place as above,
everything appears to be fine.

Not with my compiler. It looks like you're using an out of date
compiler, or you've encountered a bug in the compiler.
Very possibly I'm merely missing the intent of his meaning,
but I'm struggling to find a sense that better fits his words.
In particular, where the example he gives does not explicitly
include namespacing, thus putting f() in the global namespace,
it's hard to see exactly what "enclosing scope" it doesn't go
in to.
Note: The above has compiled for me with gcc 3.4.4 (cygwin)
and cl v. 15.00.30729.01 (VC++ Express), just in case this is
a non-standard compiler issue.

It doesn't compile with g++ 4.3.2.

Note that compilers may have delayed suppressing the injection
of friend names into the surrounding scope for fear of breaking
existing code. (At the least, I would expect a number of
versions where such code as the above triggered a warning,
rather than an error.) The fact remains that it is, according
to the standard, illegal.
 
J

James Kanze

I am not sure, whether this is always true.

Which part isn't true?
Here, »g« seems to be declared within the class specifier:
class K { friend void g(); };
void g() {}
int main() { g(); }
But when »g« is qualified, it does not seem to be declared
anymore:
class K { friend void ::g(); };
void g() {}
int main() { g(); }
"ComeauTest.c", line 1: error: the global scope has no "g"
class K { friend void ::g(); };
(gcc seems to accept this without an error message.)

But in g++, then.
One can remove the error by adding an additional declaration:
void g();
class K { friend void ::g(); };
void g() {}
int main() { g(); }

This has nothing to do with friends. After a scope resolution
operator, qualified name lookup applies, and if the name is not
found, it is an error. The first declaration of a name cannot
use a qualified name.
 
J

James Kanze

James Kanze wrote:

[...]
It's difficult to explain clearly. The fully qualified name of
the function f, after the friend declaration, is ::N::f. The
function itself is in the surrounding namespace scope. But the
friend declaration does not introduce the name into this scope;
after the friend declaration, the name is still only visible in
the class scope ::N::C.

I encountered this a while back and the best mental model I
could come up with is that non-member functions have a boolean
flag that specifies whether it is accessible normally. A
friend declaration introduces the function into the
surrounding scope, but sets this flag so that is is NOT
accessible normally (unless the function already was declared
in that scope, in which case it leaves the flag alone so that
it is accessible normally as it already was).
Declaring a function in the surrounding scope, after it's
already been declared a friend, toggles this flag so mat it's
accessible normally.

I'm not sure I understand this. The best mental model I've been
able to come up with (for me, anyway) is to distinguish between
scope and visibility. Most declarations make the symbol visible
in the scope they declare the symbol in, so there distinction is
meaning less. The friend declaration, however, declares the
name in scope ::N, but only makes it visible in the class scope
::N::C. The scope the name is declared in and the scope in
which it is visible are different. (Off hand, I can't think of
any other cases where this is the case. And I would guess that
the motivation for friend name injection, in pre-standard C++,
is that since you're declaring a name in namespace scope---file
scope back then---, it should be visible in namespace scope.)
 
P

Pascal J. Bourguignon

James Kanze said:
James Kanze wrote:
[...]
It's difficult to explain clearly. The fully qualified name of
the function f, after the friend declaration, is ::N::f. The
function itself is in the surrounding namespace scope. But the
friend declaration does not introduce the name into this scope;
after the friend declaration, the name is still only visible in
the class scope ::N::C.

I encountered this a while back and the best mental model I
could come up with is that non-member functions have a boolean
flag that specifies whether it is accessible normally. A
friend declaration introduces the function into the
surrounding scope, but sets this flag so that is is NOT
accessible normally (unless the function already was declared
in that scope, in which case it leaves the flag alone so that
it is accessible normally as it already was).
Declaring a function in the surrounding scope, after it's
already been declared a friend, toggles this flag so mat it's
accessible normally.

I'm not sure I understand this. The best mental model I've been
able to come up with (for me, anyway) is to distinguish between
scope and visibility. Most declarations make the symbol visible
in the scope they declare the symbol in, so there distinction is
meaning less. The friend declaration, however, declares the
name in scope ::N, but only makes it visible in the class scope
::N::C. The scope the name is declared in and the scope in
which it is visible are different. (Off hand, I can't think of
any other cases where this is the case. And I would guess that
the motivation for friend name injection, in pre-standard C++,
is that since you're declaring a name in namespace scope---file
scope back then---, it should be visible in namespace scope.)

using namespace N; using N::S; or just N::S are three other ways to
make a name visible in a different scope than the one it was declared
in.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,153
Members
46,699
Latest member
AnneRosen

Latest Threads

Top