More C++ Idioms/Making New Friends

Making New Friends
edit

Intent edit

To simplify creation of friend functions for a class template.

Motivation edit

Friend functions are often used to provide some auxiliary additional interfaces for a class. For example, insertion (<<), extraction (>>) operators and overloaded arithmetic operators are often friends. Declaring friend functions of a class template is a little more complicated compared to declaring a friend function for a non-template class. There are four kinds of relationships between classes and their friends when templates are involved:

  • One-to-many: A non-template function may be a friend to all template class instantiations.
  • Many-to-one: All instantiations of a template function may be friends to a regular non-template class.
  • One-to-one: A template function instantiated with one set of template arguments may be a friend to one template class instantiated with the same set of template arguments. This is also the relationship between a regular non-template class and a regular non-template friend function.
  • Many-to-many: All instantiations of a template function may be a friend to all instantiations of the template class.

The one-to-one relationship is of interest here because setting that up in C++ requires additional syntax. An example follows.

template<typename T>
class Foo {
   T value;
public:
   Foo(const T& t) { value = t; }
   friend ostream& operator<<(ostream&, const Foo<T>&);
};

template<typename T>
ostream& operator<<(ostream& os, const Foo<T>& b) {
   return os << b.value;
}

The above example is no good for us because the inserter is not a template but it still uses a template argument (T). This is a problem since it’s not a member function. The operator<<( ) must be a template so that distinct specialization for each T can be created.

Solution here is to declare a insertion operator template outside the class before friend declaration and adorn the friend declaration with <>. It indicates that a template declared earlier should be made friend.

// Forward declarations
template<class T> class Foo;
template<class T> ostream& operator<<(ostream&,
                                      const Foo<T>&);
template<class T>
class Foo {
   T value;
public:
   Foo(const T& t) { value = t; }
   friend ostream& operator<< <>(ostream&, const Foo<T>&);
};

template<class T>
ostream& operator<<(ostream& os, const Foo<T>& b)
{
   return os << b.value;
}

A disadvantage of the above solution is that it is quite verbose.

Solution and Sample Code edit

Dan Saks suggested another approach to overcome the verbosity of the above solution. His solution is known as "Making New Friends" idiom. The idea is to define the friend function inside the class template as shown below.

template<typename T>
class Foo {
   T value;
public:
   Foo(const T& t) { value = t; }
   friend ostream& operator<<(ostream& os, const Foo<T>& b)
   {
      return os << b.value;
   }
};

Such a friend function is not a template, but the template acts as a factory for "making" new friends. A new non-template function is created for each specialization of Foo.

Known Uses edit

Related Idioms edit

References edit

C++ FAQ 35.16