Friend Functions and Friend Classes
Friend declarations grant non-member functions or other classes access to a class's private and protected members — the canonical use is operator overloading and hidden-friend idiom.
Friend Declarationssince C++98A friend declaration inside a class grants a specific external function or class full access to all private and protected members, without making that function a member.
Overview
Friendship is the controlled escape hatch from C++'s access protection. It is not a design smell when used for the two purposes the language was designed around:
- Non-member operator overloading — symmetric binary operators like
operator==,operator+, and streamoperator<<must be free functions to allow implicit conversions on the left operand; they still need private access. - The hidden-friend idiom — a friend function defined inline inside the class body is found only by argument-dependent lookup (ADL), giving it a smaller namespace footprint and faster compile times.
Friendship is:
- One-way:
class Abefriendingclass Bdoes not make B aware of A. - Not inherited: a derived class does not inherit the friendship of its base.
- Not transitive: if A befriends B, and B befriends C, C has no special access to A.
Syntax
Friend function (non-member)
class Complex {
double re_, im_;
public:
Complex(double re, double im) : re_(re), im_(im) {}
// Declared friend so operator+ can access re_ and im_
friend Complex operator+(const Complex& a, const Complex& b);
};
Complex operator+(const Complex& a, const Complex& b) {
return {a.re_ + b.re_, a.im_ + b.im_}; // accesses private members
}Friend function defined inside the class (hidden friend)
class Point {
int x_, y_;
public:
Point(int x, int y) : x_(x), y_(y) {}
// Defined inside — found only by ADL, not by unqualified lookup
friend bool operator==(const Point& a, const Point& b) {
return a.x_ == b.x_ && a.y_ == b.y_;
}
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << '(' << p.x_ << ", " << p.y_ << ')';
}
};Friend class
class Engine; // forward declaration
class Car {
Engine* engine_; // Car uses Engine implementation details
friend class Engine; // Engine can access Car's private members
// ...
};Friend in a template class
template<typename T>
class Box {
T value_;
public:
explicit Box(T v) : value_(std::move(v)) {}
// Each instantiation befriends its own non-member
friend bool operator==(const Box& a, const Box& b) {
return a.value_ == b.value_;
}
};Hidden-Friend Idiom
Defining a friend function inside the class body has three effects:
- The function is injected into the enclosing namespace but is not visible to unqualified lookup — only to ADL when one of its arguments is of the class type.
- This reduces namespace pollution and avoids name collision with unrelated functions.
- Overload resolution is faster because fewer candidates appear in lookup.
class Token {
std::string value_;
public:
explicit Token(std::string v) : value_(std::move(v)) {}
// Hidden friend — only reachable via ADL
friend bool operator==(const Token& a, const Token& b) {
return a.value_ == b.value_;
}
};
Token a("foo"), b("foo");
bool eq = (a == b); // found via ADL: one argument is Token
// bool eq2 = operator==(a,b); // also works (qualified)
// bool eq3 = ::operator==(a,b); // NOT found — not in global namespace by unqualified lookupThis pattern is the idiomatic way to write comparison operators and swap in modern C++.
Stream Insertion Operator
operator<< cannot be a member because std::ostream is the left operand and you cannot add members to it. The canonical implementation is a hidden friend:
class Rect {
int w_, h_;
public:
Rect(int w, int h) : w_(w), h_(h) {}
friend std::ostream& operator<<(std::ostream& os, const Rect& r) {
return os << w_ << 'x' << h_;
}
};Common Pitfalls
Overusing friendship to avoid redesign
If you repeatedly need friend access to a class, consider whether the private data should be moved to a protected section or whether a public accessor is more appropriate. Friendship should be exceptional, not pervasive.
Declaring friend in the wrong class
class A { friend class B; }; // B can see A's privates
class B { friend class A; }; // A can see B's privates (separate declaration needed)Confusing with inheritance
A derived class D : public B does not inherit B's friendship with C. If D needs access to C's privates, C must explicitly befriend D.
Forward-declaring a friend incorrectly
When a friend function is defined outside the class and the class is in a namespace, the friend declaration and definition must both be in scope:
namespace geo {
class Point;
bool operator==(const Point&, const Point&);
class Point {
int x_, y_;
friend bool operator==(const Point&, const Point&); // declares the one above
};
bool operator==(const Point& a, const Point& b) { return a.x_ == b.x_; }
}Friend Classes in Depth
The friend class declaration grants every member of the named class access to all private and protected members of the class granting friendship.
class BankAccount {
double balance_;
std::string owner_;
friend class Auditor; // Auditor can read balance_ and owner_
friend class AccountDB; // AccountDB can modify them
public:
BankAccount(std::string owner, double initial)
: owner_(std::move(owner)), balance_(initial) {}
};
class Auditor {
public:
void verify(const BankAccount& acct) {
// OK — balance_ is private but Auditor is a friend
if (acct.balance_ < 0)
throw std::runtime_error("Negative balance for " + acct.owner_);
}
};Typical use cases for friend class:
| Pattern | Why friendship is needed |
|---|---|
| Iterator / container pair | std::list<T>::iterator needs access to the node pointer stored privately in std::list<T> |
| Factory class | A ConnectionFactory needs to call a private Connection constructor so nothing else can construct one |
| Test fixture | FooTest needs to reach private implementation details without adding public test hooks to production code |
| Serialization helper | A Serializer<T> needs field-level access without making every field public |
Friend Class Templates
When you want to befriend an entire family of template specializations, put the template header inside the friend declaration:
template<typename T>
class Matrix {
T data_[4][4];
// Every specialization of Serializer<U> becomes a friend
template<typename U>
friend class Serializer;
// Only Matrix<T> itself is a friend of each other specialization
template<typename U>
friend class Matrix; // Matrix<double> can access Matrix<float>'s privates
public:
explicit Matrix(T fill = T{}) {
for (auto& row : data_)
for (auto& cell : row) cell = fill;
}
};Per cppreference [temp.friend]: "every specialization of the template becomes a friend, whether it is implicitly instantiated, partially specialized, or explicitly specialized."
To befriend only a single, fully-specified specialization (not the whole template family), name the full specialization directly:
template<typename T> class Cache {}; // primary template
class Config {
int timeout_;
friend class Cache<int>; // OK — only Cache<int>, not Cache<double>
// template<typename T>
// friend class Cache<T*>; // ERROR — partial specializations cannot be friends
};C++20 Rewritten Expressions and Friend Operators
C++20 redesigned comparison resolution around two primary operators (== and <=>) and four secondary operators (!=, <, >, <=, >=). This interacts directly with how friend comparison operators are written.
The operator== rewrite rules
When you write a single hidden-friend operator==, C++20 synthesizes three additional expressions for free:
class Version {
int major_, minor_;
public:
Version(int maj, int min) : major_(maj), minor_(min) {}
// One hidden friend — no operator!= needed
friend bool operator==(const Version& a, const Version& b) {
return a.major_ == b.major_ && a.minor_ == b.minor_;
}
};
Version v1{1, 0}, v2{2, 0};
bool eq = (v1 == v2); // direct: operator==(v1, v2)
bool ne = (v1 != v2); // rewritten: !(operator==(v1, v2))
bool eq2 = (v2 == v1); // reversed: operator==(v1, v2) — equality is symmetric
bool ne2 = (v2 != v1); // rewritten + reversed: !(operator==(v1, v2))The four candidate transformations for == / != (from Barry Revzin's analysis of the C++20 spec):
| Expression | Primary form tried | Secondary rewrite |
|---|---|---|
a == b | operator==(a, b) | reversed: operator==(b, a) |
a != b | operator!=(a, b) | rewritten: !(operator==(a, b)), then !(operator==(b, a)) |
No new functions are generated — these are source-level rewrites in the candidate set. If you also define operator!= explicitly, it wins over the rewrite because non-rewritten candidates are preferred.
The operator<=> rewrite rules — all six comparisons from one function
operator<=> (the spaceship operator) is the ordering primary. Defining it as a hidden friend, combined with operator==, gives you all six comparison operators:
#include <compare>
class Rational {
int num_, den_;
public:
explicit Rational(int n, int d = 1) : num_(n), den_(d) {}
friend bool operator==(const Rational& a, const Rational& b) {
return a.num_ * b.den_ == b.num_ * a.den_;
}
friend std::strong_ordering operator<=>(const Rational& a, const Rational& b) {
// cross-multiply to compare without division
auto lhs = a.num_ * b.den_;
auto rhs = b.num_ * a.den_;
return lhs <=> rhs;
}
};
Rational half{1, 2}, third{1, 3};
// Directly defined:
bool eq = (half == third); // operator==(half, third)
auto cmp = (half <=> third); // operator<=>(half, third)
// Rewritten by the compiler from operator<=>:
bool lt = (half < third); // (half <=> third) < 0
bool gt = (half > third); // (half <=> third) > 0
bool le = (half <= third); // (half <=> third) <= 0
bool ge = (half >= third); // (half <=> third) >= 0
// Reversed + rewritten for heterogeneous comparison — no extra overloads neededThe complete rewrite table (cppreference / P0515R3, preferred form first):
| Expression written | First alternative tried | Second alternative tried |
|---|---|---|
a == b | b == a | — |
a != b | !(a == b) | !(b == a) |
a <=> b | 0 <=> (b <=> a) | — |
a < b | (a <=> b) < 0 | (b <=> a) > 0 |
a <= b | (a <=> b) <= 0 | (b <=> a) >= 0 |
a > b | (a <=> b) > 0 | (b <=> a) < 0 |
a >= b | (a <=> b) >= 0 | (b <=> a) <= 0 |
The practical upshot: define operator== and operator<=> as hidden friends; the compiler handles the other four.
Defaulting both operators
For value types whose members all support <=>, you can = default both primaries and get correct member-wise comparison at zero cost:
struct Point {
int x, y;
friend bool operator==(const Point&, const Point&) = default;
friend auto operator<=>(const Point&, const Point&) = default;
};
// Now Point supports ==, !=, <, <=, >, >= out of the boxHidden Friend Idiom — Mechanics
The "hidden" in hidden friend refers to a specific ADL rule: a function defined (not just declared) inside a class body with the friend keyword is injected into the enclosing namespace but is not visible to unqualified lookup — only to ADL when one of the call's arguments belongs to the associated class.
The consequences, from Anthony Williams ("The Power of Hidden Friends in C++", 2019):
-
No accidental implicit conversions. If
class Yis convertible toclass X, andoperator==(X, X)is a hidden friend ofX, theny1 == y2(where both areY) does not compile — the operator is invisible to lookup unless an actualXis in the argument list. A namespace-scope declaration would silently allow the conversion. -
Smaller overload resolution set. A large codebase may have thousands of
operator==overloads. Hidden friends are only considered when an argument of the relevant type is present, so the compiler has fewer candidates to rank on every==expression. -
No qualified-name access.
::operator==(a, b)will not find a hidden friend — global qualified lookup does not trigger ADL. Only the unqualified calla == bdoes.
Visibility rules for hidden friends (must satisfy all four):
- The function must be a function or function template, not a callable object.
- The call site must use an unqualified name.
- Ordinary unqualified lookup must not find a non-function entity of the same name.
- At least one argument must be of a user-defined type (or a pointer/reference to one).
struct Token {
std::string value_;
explicit Token(std::string v) : value_(std::move(v)) {}
friend bool operator==(const Token& a, const Token& b) {
return a.value_ == b.value_;
}
};
Token t1{"hello"}, t2{"hello"};
bool ok1 = (t1 == t2); // found via ADL — t1/t2 are Token
// bool ok2 = (operator==(t1, t2)); // also valid — ADL still triggered by args
// bool ok3 = (::operator==(t1, t2)); // ERROR — qualified lookup, no ADLHidden friends via base-class mixins
Because the ADL lookup list for a type includes all its base classes, you can inject hidden friends through a mixin — a technique used by libraries like Boost.Operators and jss::strong_typedef:
template<typename Derived>
struct EqualityComparable {
friend bool operator!=(const Derived& a, const Derived& b) {
return !(a == b); // reuses whatever operator== Derived provides
}
};
class Uuid : public EqualityComparable<Uuid> {
uint64_t hi_, lo_;
public:
Uuid(uint64_t h, uint64_t l) : hi_(h), lo_(l) {}
friend bool operator==(const Uuid& a, const Uuid& b) {
return a.hi_ == b.hi_ && a.lo_ == b.lo_;
}
// operator!= comes from EqualityComparable<Uuid> for free
};The ADL lookup for a Uuid argument includes EqualityComparable<Uuid> as a base class, so operator!= defined there is found. Note: in C++20 this specific example is superseded by the operator== rewrite rule, but the mixin pattern remains useful for other operations (swap, print, hash_value, etc.).
See Also
- ADL (Argument-Dependent Lookup) — why hidden friends are found at all when called without qualification
- Operator Overloading — which operators are typically implemented as friends vs. members
- const Correctness — friend functions must also respect const on the argument