Выбрать главу

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 of course 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. Of course, 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 class 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."[61] This is the most convenient way to define friend functions for templates.

To make this perfectly clear, suppose you have a class template to which you want to add non-member operators as friends. 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 likes of 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 (see 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 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 << ']';

  }

};