Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Templates and friends

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. [68] 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

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire