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.