Skip to content
C++
Language
since C++98
Basic

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++98

A 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:

  1. Non-member operator overloading — symmetric binary operators like operator==, operator+, and stream operator<< must be free functions to allow implicit conversions on the left operand; they still need private access.
  2. 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 A befriending class B does 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)

cpp
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)

cpp
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

cpp
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

cpp
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:

  1. 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.
  2. This reduces namespace pollution and avoids name collision with unrelated functions.
  3. Overload resolution is faster because fewer candidates appear in lookup.
cpp
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 lookup

This 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:

cpp
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

cpp
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:

cpp
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.

cpp
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:

PatternWhy friendship is needed
Iterator / container pairstd::list<T>::iterator needs access to the node pointer stored privately in std::list<T>
Factory classA ConnectionFactory needs to call a private Connection constructor so nothing else can construct one
Test fixtureFooTest needs to reach private implementation details without adding public test hooks to production code
Serialization helperA 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:

cpp
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:

cpp
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:

cpp
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):

ExpressionPrimary form triedSecondary rewrite
a == boperator==(a, b)reversed: operator==(b, a)
a != boperator!=(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:

cpp
#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 needed

The complete rewrite table (cppreference / P0515R3, preferred form first):

Expression writtenFirst alternative triedSecond alternative tried
a == bb == a—
a != b!(a == b)!(b == a)
a <=> b0 <=> (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:

cpp
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 box

Hidden 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):

  1. No accidental implicit conversions. If class Y is convertible to class X, and operator==(X, X) is a hidden friend of X, then y1 == y2 (where both are Y) does not compile — the operator is invisible to lookup unless an actual X is in the argument list. A namespace-scope declaration would silently allow the conversion.

  2. 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.

  3. No qualified-name access. ::operator==(a, b) will not find a hidden friend — global qualified lookup does not trigger ADL. Only the unqualified call a == b does.

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).
cpp
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 ADL

Hidden 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:

cpp
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