Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
A friend function declaration inside a class allows a
non-member function to access non-public members of that class. If the friend
function name is qualified, it will be found in the namespace or class that
qualifies it. If it is unqualified, however, the compiler must make an
assumption about where the definition of the friend function will be, since all
identifiers must have a unique scope. The expectation is that the function will
be defined in the nearest enclosing namespace (non-class) scope that contains
the class granting friendship. Often this is just the global scope. The
following non-template example clarifies this issue:
//: C05:FriendScope.cpp
#include <iostream>
using namespace std;
class Friendly {
int i;
public:
Friendly(int theInt) { i = theInt; }
friend void f(const Friendly&); // Needs global
def.
void g() { f(*this); }
};
void h() {
f(Friendly(1)); // Uses ADL
}
void f(const Friendly& fo) { // Definition of
friend
cout << fo.i << endl;
}
int main() {
h(); // Prints 1
Friendly(2).g(); // Prints 2
} ///:~
The declaration of f( ) inside the Friendly
class is unqualified, so the compiler will expect to be able to eventually link
that declaration to a definition at file scope (the namespace scope that
contains Friendly in this case). That definition appears after the
definition of the function h( ). The linking of the call to f( )
inside h( ) to the same function is a separate matter, however.
This is resolved by ADL. Since the argument of f( ) inside h( )
is a Friendly object, the Friendly class is searched for a
declaration of f( ), which succeeds. If the call were f(1)
instead (which makes some sense since 1 can be implicitly converted to Friendly(1)),
the call should fail, since there is no hint of where the compiler should look
for the declaration of f( ). The EDG compiler correctly complains
that f is undefined in that case.
Now suppose that Friendly and f are both
templates, as in the following program:
//: C05:FriendScope2.cpp
#include <iostream>
using namespace std;
// Necessary forward declarations:
template<class T> class Friendly;
template<class T> void f(const
Friendly<T>&);
template<class T> class Friendly {
T t;
public:
Friendly(const T& theT) : t(theT) {}
friend void f<>(const Friendly<T>&);
void g() { f(*this); }
};
void h() {
f(Friendly<int>(1));
}
template<class T> void f(const
Friendly<T>& fo) {
cout << fo.t << endl;
}
int main() {
h();
Friendly<int>(2).g();
} ///:~
First notice that angle brackets in the declaration of f
inside Friendly. This is necessary to tell the compiler that f is
a template. Otherwise, the compiler will look for an ordinary function named f
and not find it. We could have inserted the template parameter (<T>)
in the brackets, but it is easily deduced from the declaration.
The forward declaration of the function template f
before the class definition is necessary, even though it wasn t in the previous
example when f was a not a template; the language specifies that friend
function templates must be previously declared. To properly declare f, Friendly
must also have been declared, since f takes a Friendly argument,
hence the forward declaration of Friendly in the beginning. We could
have placed the full definition of f right after the initial declaration
of Friendly instead of separating its definition and declaration, but we
chose instead to leave it in a form that more closely resembles the previous
example.
One last option remains for using friends inside templates:
fully define them inside the host class template definition itself. Here is how
the previous example would appear with that change:
//: C05:FriendScope3.cpp {-bor}
// Microsoft: use the -Za (ANSI-compliant) option
#include <iostream>
using namespace std;
template<class T> class Friendly {
T t;
public:
Friendly(const T& theT) : t(theT) {}
friend void f(const Friendly<T>& fo) {
cout << fo.t << endl;
}
void g() { f(*this); }
};
void h() {
f(Friendly<int>(1));
}
int main() {
h();
Friendly<int>(2).g();
} ///:~
There is an important difference between this and the
previous example: f is not a template here, but is an ordinary function.
(Remember that angle brackets were necessary before to imply that f( )
was a template.) Every time the Friendly class template is instantiated,
a new, ordinary function overload is created that takes an argument of the
current Friendly specialization. This is what Dan Saks has called
making new friends. This
is the most convenient way to define friend functions for templates.
To clarify, suppose you want to add non-member friend
operators to a class template. Here is a class template that simply holds a
generic value:
template<class T> class Box {
T t;
public:
Box(const T& theT) : t(theT) {}
};
Without understanding the previous examples in this section,
novices find themselves frustrated because they can t get a simple stream
output inserter to work. If you don t define your operators inside the
definition of Box, you must provide the forward declarations we showed
earlier:
//: C05:Box1.cpp
// Defines template operators.
#include <iostream>
using namespace std;
// Forward declarations
template<class T> class Box;
template<class T>
Box<T> operator+(const Box<T>&, const
Box<T>&);
template<class T>
ostream& operator<<(ostream&, const
Box<T>&);
template<class T> class Box {
T t;
public:
Box(const T& theT) : t(theT) {}
friend Box operator+<>(const Box<T>&,
const Box<T>&);
friend ostream& operator<< <>(ostream&,
const Box<T>&);
};
template<class T>
Box<T> operator+(const Box<T>& b1,
const Box<T>& b2) {
return Box<T>(b1.t + b2.t);
}
template<class T>
ostream& operator<<(ostream& os, const
Box<T>& b) {
return os << '[' << b.t << ']';
}
int main() {
Box<int> b1(1), b2(2);
cout << b1 + b2 << endl; // [3]
// cout << b1 + 2 << endl; // No implicit
conversions!
} ///:~
Here we are defining both an addition operator and an output
stream operator. The main program reveals a disadvantage of this approach: you
can t depend on implicit conversions (the expression b1 + 2) because
templates do not provide them. Using the in-class, non-template approach is
shorter and more robust:
//: C05:Box2.cpp
// Defines non-template operators.
#include <iostream>
using namespace std;
template<class T> class Box {
T t;
public:
Box(const T& theT) : t(theT) {}
friend Box<T> operator+(const Box<T>&
b1,
const Box<T>& b2) {
return Box<T>(b1.t + b2.t);
}
friend ostream&
operator<<(ostream& os, const
Box<T>& b) {
return os << '[' << b.t << ']';
}
};
int main() {
Box<int> b1(1), b2(2);
cout << b1 + b2 << endl; // [3]
cout << b1 + 2 << endl; // [3]
} ///:~
Because the operators are normal functions (overloaded for
each specialization of Box just int in this case), implicit
conversions are applied as normal; so the expression b1 + 2 is valid.
Note that there s one type in particular that cannot be made
a friend of Box, or any other class template for that matter, and that
type is T or rather, the type that the class template is parameterized
upon. To the best of our knowledge, there are really no good reasons why this
shouldn t be allowed, but as is, the declaration friend class T is
illegal, and should not compile.
Friend templates
You can be precise as to which specializations of a template
are friends of a class. In the examples in the previous section, only the
specialization of the function template f with the same type that
specialized Friendly was a friend. For example, only the specialization f<int>(const
Friendly<int>&) is a friend of the class Friendly<int>.
This was accomplished by using the template parameter for Friendly to
specialize f in its friend declaration. If we had wanted to, we could
have made a particular, fixed specialization of f a friend to all
instances of Friendly, like this:
// Inside Friendly:
friend void f<>(const Friendly<double>&);
By using double instead of T, the double
specialization of f has access to the non-public members of any Friendly
specialization. The specialization f<double>( ) still isn t
instantiated unless it is explicitly called.
Likewise, if you declare a non-template function with no
parameters dependent on T, that single function is a friend to all
instances of Friendly:
// Inside Friendly:
friend void g(int); // g(int) befriends all Friendlys
As always, since g(int) is unqualified, it must be
defined at file scope (the namespace scope containing Friendly).
It is also possible to arrange for all specializations of f
to be friends for all specializations of Friendly, with a so-called friend
template, as follows:
template<class T> class Friendly {
template<class U> friend void f<>(const Friendly<U>&);
Since the template argument for the friend declaration is
independent of T, any combination of T and U is allowed,
achieving the friendship objective. Like member templates, friend templates can
appear within non-template classes as well.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |