C++ Friend Declarations in Template Classes

In the C++ programming language, declaring a friend inside a class template alters how functions or classes are granted access to private and protected members across instantiations. Depending on whether the declared entity is a non-template, a specific template specialization, or an entire template family, the scope and visibility of the friendship change significantly. This article examines the core behaviors of friend declarations within template classes, covering non-template friends, the hidden friend idiom, bound versus unbound template relationships, and common pitfalls like duplicate definitions.

Non-Template Friend Functions and Hidden Friends

When a non-template function is declared as a friend inside a class template, each instantiation of that template produces a distinct non-template function.

template <typename T>
class Box {
    T value;
    // Declares a separate non-template function for each type T
    friend void printBox(const Box<T>& b);
};

If you define this function inside the class definition, it becomes a "hidden friend":

template <typename T>
class Box {
    T value;
public:
    Box(T val) : value(val) {}

    // Defined inline as a hidden friend
    friend void inspect(const Box<T>& b) {
        std::cout << b.value << '\n';
    }
};

Hidden friends are not visible via normal unqualified or qualified name lookup; they can only be found via Argument-Dependent Lookup (ADL). This pattern is especially useful for operator overloads (such as operator<< or arithmetic operators), as it keeps the enclosing namespace clean and avoids unwanted implicit conversions.

Pitfall: If you define a non-template friend function inside a template class without referencing the template parameter T in its signature, instantiating the template more than once will generate multiple identical function definitions, resulting in a compilation error due to violating the One Definition Rule (ODR).

Bound Friend Templates (One-to-One Friendship)

A bound friend establishes a one-to-one relationship between a specific instantiation of the class template and a matching instantiation of a friend function or class.

To grant friendship to a specific template specialization, the friend template must be forward-declared before the class template:

// Forward declarations
template <typename T> class Container;
template <typename T> void process(const Container<T>&);

template <typename T>
class Container {
    T data;

    // Grants friendship only to the matching specialization process<T>
    friend void process<T>(const Container<T>&);
};

In this setup, process<int> can access the private members of Container<int>, but process<double> cannot.

Unbound Friend Templates (Many-to-Many Friendship)

An unbound friend declaration grants friendship to all specializations of a template, not just the one that matches the class’s template arguments. This is achieved by including a new template parameter list directly inside the friend declaration.

template <typename T>
class Container {
    T data;

    // Any specialization of process() is a friend of Container<T>
    template <typename U>
    friend void process(const Container<U>&);

    // Any specialization of Node is a friend of Container<T>
    template <typename U>
    friend class Node;
};

This approach allows Container<int> to grant access to Node<double>, Node<std::string>, and any other specialization of Node. It is commonly used when one generic type needs to construct or copy another instance of the same template instantiated with a different type (such as implementing generalized copy constructors).

Non-Template Class Friends

Declaring a non-template class as a friend inside a class template gives that non-template class access to the private members of all instantiations of the template:

class Inspector;

template <typename T>
class DataWrapper {
    T item;
    friend class Inspector; // Inspector can access DataWrapper<int>, DataWrapper<std::string>, etc.
};

Every instantiated version of DataWrapper treats Inspector as a friend.

Summary of Rules

  • Inline Non-Template Friend: Creates a distinct non-template function accessible via ADL for each instantiation.
  • Bound Friend (friend void func<T>(...);): Restricts access to a specific matching specialization; requires forward declarations.
  • Unbound Friend (template <typename U> friend class ...;): Grants access to all specializations of the friend template.
  • Non-Template Class Friend (friend class Name;): Grants access to that single class across every instantiation of the template.