What Are Friend Non-Member Functions?

A friend non-member function is a standalone function that one class has singled out and waved past its access controls, so it may read and write whatever that class keeps private or protected. The permission is written inside the class body, using the friend keyword, and it applies to that one named function only.

Everything else about such a function stays ordinary. It has no implicit object, it is not called with the dot operator, it is not qualified with the class name, and it cannot be const. The single thing that changes is that the compiler stops rejecting its accesses to non-public members.

This lesson covers friendship granted to non-member functions. The next lesson, on friend classes and friend member functions, covers the other two things a class can befriend.

Where Access Control Runs Out

Consider a wheel-building record for a bicycle workshop. It keeps the number of spokes laced so far and the tension the builder has settled on, both private, because nothing outside the class should be writing those numbers directly.

Printing a one-line summary is not really the record's job. It is presentation, it will grow options over time, and keeping it outside the class keeps the class small. So we write it as a plain non-member function.

That does not compile. This example is broken on purpose:

#include <iostream>

class WheelBuild
{
private:
    int m_spokesLaced{ 0 };
    int m_tensionKgf{ 0 };

public:
    void lace(int spokes) { m_spokesLaced += spokes; }
    void tightenTo(int kgf) { m_tensionKgf = kgf; }
};

void reportBuild(const WheelBuild& build)
{
    std::cout << build.m_spokesLaced << " spokes tensioned to " << build.m_tensionKgf << " kgf\n";
}

int main()
{
    WheelBuild front{};
    front.lace(32);
    front.tightenTo(110);

    reportBuild(front);

    return 0;
}
s.cpp: In function 'void reportBuild(const WheelBuild&)':
s.cpp:16:24: error: 'int WheelBuild::m_spokesLaced' is private within this context
   16 |     std::cout << build.m_spokesLaced << " spokes tensioned to " << build.m_tensionKgf << " kgf\n";
      |                        ^~~~~~~~~~~~~
s.cpp:6:9: note: declared private here
    6 |     int m_spokesLaced{ 0 };
      |         ^~~~~~~~~~~~~

There are three ways out of this, and they are worth naming before we pick one.

  1. Move the function into the class. It works, but it grows the interface with something that is not really about wheel building, and the same argument will be made by the next helper, and the one after that.
  2. Add public access functions for whatever the helper needs. Often the right answer, and we return to it at the end of this lesson.
  3. Have the class name the helper as a friend. This is the option that costs nothing in the public interface, because the permission is not visible as a callable member.

Option 2 stops being attractive when the values a helper needs are implementation details that no other caller should see, or when it would take half a dozen new accessors that exist for one function's benefit.

Granting Access With a Friend Declaration

One line inside the class fixes the program above:

#include <iostream>

class WheelBuild
{
private:
    int m_spokesLaced{ 0 };
    int m_tensionKgf{ 0 };

public:
    void lace(int spokes) { m_spokesLaced += spokes; }
    void tightenTo(int kgf) { m_tensionKgf = kgf; }

    // WheelBuild hands reaching-in rights to reportBuild(), nothing else
    friend void reportBuild(const WheelBuild& build);
};

void reportBuild(const WheelBuild& build)
{
    // Being a friend, reportBuild() may read what is hidden here
    std::cout << build.m_spokesLaced << " spokes tensioned to " << build.m_tensionKgf << " kgf\n";
}

int main()
{
    WheelBuild front{};
    front.lace(32);
    front.tightenTo(110);

    reportBuild(front);

    return 0;
}

Output:

32 spokes tensioned to 110 kgf

The declaration names a full signature, so friendship is granted to that function and no other. A different reportBuild() taking different parameters would get nothing.

Because a friend non-member function has no implicit object, the object it works on has to arrive as a parameter. That is why reportBuild() takes a const WheelBuild& and why the call is reportBuild(front) rather than front.reportBuild().

Key Concept
Friendship is granted, never taken. Only the class whose members are being accessed can write the friend declaration, so a class keeps complete control over who reaches inside it. No function can declare itself a friend of a class.

The Declaration Can Sit Anywhere in the Class

A friend declaration is not a member, so the public: and private: labels have no effect on it. Placing it in the private section grants exactly the same access as placing it in the public section:

#include <iostream>

class SpokeSet
{
private:
    int m_lengthMm{};

    // Sitting here grants exactly what sitting below public: would
    friend void printLength(const SpokeSet& spokes);

public:
    explicit SpokeSet(int lengthMm) : m_lengthMm{ lengthMm } {}
};

void printLength(const SpokeSet& spokes)
{
    std::cout << "Cut to " << spokes.m_lengthMm << " mm\n";
}

int main()
{
    SpokeSet driveSide{ 293 };

    printLength(driveSide);

    return 0;
}

Output:

Cut to 293 mm

Since the position carries no meaning to the compiler, pick one spot and stay consistent, so a reader can find every grant a class makes by looking in the same place.

Writing the Body Inside the Class

A friend function may also be defined where it is declared, inside the class:

#include <iostream>

class WheelBuild
{
private:
    int m_spokesLaced{ 0 };
    int m_tensionKgf{ 0 };

public:
    void lace(int spokes) { m_spokesLaced += spokes; }
    void tightenTo(int kgf) { m_tensionKgf = kgf; }

    // Written in place, yet it belongs to no class
    friend void reportBuild(const WheelBuild& build)
    {
        std::cout << build.m_spokesLaced << " spokes tensioned to " << build.m_tensionKgf << " kgf\n";
    }
};

int main()
{
    WheelBuild rear{};
    rear.lace(28);
    rear.tightenTo(120);

    reportBuild(rear);

    return 0;
}

Output:

28 spokes tensioned to 120 kgf

The placement is misleading, so let us be blunt about it: this is still a non-member function. It behaves as though it had been written after the closing brace of the class. It has no implicit object, and calling it through one fails. This example is broken on purpose:

#include <iostream>

class WheelBuild
{
private:
    int m_spokesLaced{ 0 };

public:
    void lace(int spokes) { m_spokesLaced += spokes; }

    friend void reportBuild(const WheelBuild& build)
    {
        std::cout << build.m_spokesLaced << " spokes laced\n";
    }
};

int main()
{
    WheelBuild rear{};
    rear.lace(28);

    rear.reportBuild(); // reportBuild() is not a member

    return 0;
}
s.cpp: In function 'int main()':
s.cpp:22:10: error: 'class WheelBuild' has no member named 'reportBuild'
   22 |     rear.reportBuild(); // reportBuild() is not a member
      |          ^~~~~~~~~~~
Warning
A friend function defined inside a class is not visible to ordinary name lookup in the enclosing namespace. The compiler finds it by looking at the classes of the arguments you pass, so a call like reportBuild(rear) works, while a call with no WheelBuild argument will not find the function at all. If you want a name that behaves in every respect like a normal function, declare it as a friend and define it outside the class.

Member or Non-Member: The Symmetry Argument

Access is not the only reason to reach for a friend. Sometimes a non-member simply reads better, and the clearest case is a function that treats two objects of the same class as equals.

Here two rims are compared by their effective rim diameter, once as a member function and once as a friend non-member function:

#include <iostream>

class Rim
{
private:
    int m_erdMm{};

public:
    explicit Rim(int erdMm) : m_erdMm{ erdMm } {}

    bool interchangeableWith(const Rim& other) const;

    friend bool sameErd(const Rim& first, const Rim& second);
};

bool Rim::interchangeableWith(const Rim& other) const
{
    return m_erdMm == other.m_erdMm;
}

bool sameErd(const Rim& first, const Rim& second)
{
    return first.m_erdMm == second.m_erdMm;
}

int main()
{
    Rim boxSection{ 597 };
    Rim deepSection{ 597 };
    Rim tandemRim{ 604 };

    std::cout << std::boolalpha;
    std::cout << boxSection.interchangeableWith(deepSection) << '\n';
    std::cout << sameErd(boxSection, tandemRim) << '\n';

    return 0;
}

Output:

true
false

The two functions do the same work. What differs is how balanced they look:

Member interchangeableWith() Friend sameErd()
Operands one implicit, one explicit both explicit
Body m_erdMm == other.m_erdMm first.m_erdMm == second.m_erdMm
Call boxSection.interchangeableWith(deepSection) sameErd(boxSection, tandemRim)
Reads as one rim asked about another two rims compared

In the member version, one of the two m_erdMm references belongs to the hidden object and the other belongs to the parameter, so the reader has to hold that asymmetry in their head. In the non-member version, every member access is prefixed by a named parameter and the two sides match.

The calling syntax is a matter of taste, and plenty of people prefer the member form. The argument gets much stronger with operator overloading. A member operator always takes its left operand as the implicit object, which rules out any conversion on that side, so the symmetric non-member form is frequently the only one that works for both operands.

One Function, Two Classes

Nothing limits a function to being the friend of a single class. Each class grants friendship independently, and a function that is named by both gets private access to both.

A spoke length calculation needs numbers from two separate objects, the hub and the rim. It belongs to neither, so a non-member function is the natural home for it:

#include <iostream>

class Rim; // Rim arrives later; Hub needs only its name now

class Hub
{
private:
    int m_flangeDiameterMm{};

public:
    explicit Hub(int flangeDiameterMm) : m_flangeDiameterMm{ flangeDiameterMm } {}

    friend void printRadialSpan(const Hub& hub, const Rim& rim);
};

class Rim
{
private:
    int m_erdMm{};

public:
    explicit Rim(int erdMm) : m_erdMm{ erdMm } {}

    friend void printRadialSpan(const Hub& hub, const Rim& rim);
};

void printRadialSpan(const Hub& hub, const Rim& rim)
{
    int span{ (rim.m_erdMm - hub.m_flangeDiameterMm) / 2 };
    std::cout << "Radial span " << span << " mm per spoke\n";
}

int main()
{
    Hub rearHub{ 45 };
    Rim boxSection{ 597 };

    printRadialSpan(rearHub, boxSection);

    return 0;
}

Output:

Radial span 276 mm per spoke

Note that the grant has to be written twice, once in each class. Friendship is per class, and Hub naming the function says nothing about what Rim allows.

The line sitting above both classes is the other detail worth studying:

class Rim;

This is a class forward declaration. It plays the same role as a function forward declaration, promising the compiler that the name will be defined later, and because a class has no return type or parameter list, the form is always just class Name. It is needed here because Hub's friend declaration mentions Rim before Rim exists.

Remove the forward declaration from the program above and the friend declaration inside Hub can no longer be parsed:

s.cpp:11:55: error: 'Rim' does not name a type
   11 |     friend void printRadialSpan(const Hub& hub, const Rim& rim);
      |                                                       ^~~
s.cpp: In function 'void printRadialSpan(const Hub&, const Rim&)':
s.cpp:27:34: error: 'int Hub::m_flangeDiameterMm' is private within this context
   27 |     int span{ (rim.m_erdMm - hub.m_flangeDiameterMm) / 2 };
      |                                  ^~~~~~~~~~~~~~~~~~

The second error follows from the first. Because the friend declaration failed, Hub never granted anything, so the function is left as an outsider poking at a private member.

Does Friendship Break Encapsulation?

No. Encapsulation is about a class controlling access to its own state, and a friend declaration is that control being exercised, not bypassed. The class chose the friend, named it exactly, and can withdraw the grant by deleting one line. Treat a friend as part of the class's implementation that happens to live outside the braces, and the access stops looking like a leak.

What friendship does cost is coupling. A friend is written against the class's internals, so every rename, retype, or restructuring of those internals is a change the friend has to absorb too. A class with a long list of friends has a correspondingly long list of places to fix when its representation changes.

That cost is reducible. A friend is allowed to reach directly into the class, but nothing forces it to. Wherever the public interface already exposes what the friend needs, using it keeps that part of the friend insulated from future changes.

Best Practice
Inside a friend function, use the class's public interface wherever it is sufficient, and fall back to direct member access only for what the interface does not provide.

Choosing Between a Friend and an Access Function

Friendship is one tool for a problem that usually has a cheaper solution. Work down this table before writing the friend keyword:

Situation Reach for
A suitable public access function already exists A plain non-member function, no friendship
One small access function would be a reasonable addition to the interface The access function, no friendship
The helper needs internals that should never become public A friend declaration
The helper would need a pile of accessors nobody else would ever call A friend declaration
The helper treats two objects of the class symmetrically A friend non-member function

The very first reportBuild() example falls into the second row. A wheel-building record having public accessors for its spoke count and its tension is entirely reasonable, so the friendship can be dropped:

#include <iostream>

class WheelBuild
{
private:
    int m_spokesLaced{ 0 };
    int m_tensionKgf{ 0 };

public:
    void lace(int spokes) { m_spokesLaced += spokes; }
    void tightenTo(int kgf) { m_tensionKgf = kgf; }

    int spokesLaced() const { return m_spokesLaced; }
    int tensionKgf() const { return m_tensionKgf; }
};

void reportBuild(const WheelBuild& build) // no friendship required
{
    std::cout << build.spokesLaced() << " spokes tensioned to " << build.tensionKgf() << " kgf\n";
}

int main()
{
    WheelBuild front{};
    front.lace(32);
    front.tightenTo(110);

    reportBuild(front);

    return 0;
}

Output:

32 spokes tensioned to 110 kgf

Same output, one fewer coupling. Rename m_tensionKgf now and only the class needs editing, because reportBuild() never knew the member's name.

Best Practice
Whenever a non-friend version is workable and sensible, write that one, leaning on the public interface. Hand out friendship only where the interface cannot supply what a function needs, or where supplying it would clutter that interface with accessors nobody else wants.

The judgement in that last clause matters. Every public member, however trivial, is one more thing the class promises and one more thing a reader has to take in. Two sensible accessors are cheaper than a friend declaration. Eight accessors added so that one report function can do its job are not, and a friend is the better trade.

Summary

Point What to remember
What a friend is A function granted access to a class's private and protected members, treated for access purposes as though it were a member
Who grants it Only the class being accessed, through a friend declaration in its body
Where the declaration goes Anywhere inside the class body, because access specifiers do not apply to it
Calling a friend non-member Like any free function, with the object passed explicitly, since there is no implicit object
Defined inside the class Still a non-member, found through the types of its arguments rather than by ordinary lookup
Multiple classes A single function can be a friend of any number of classes, each granting independently
Naming a class not yet defined Use a forward declaration of the form class Name; before the class that mentions it
Encapsulation Not violated, because the class chose the friend deliberately and can revoke the grant
The real cost Coupling to internals, so prefer the public interface inside friends, and prefer non-friends where reasonable

Friend non-member functions exist for the cases where separating a job from a class is the right design but the job still needs the class's internals. Used sparingly they keep interfaces small and implementations honest, and they become routine once you reach operator overloading, where the symmetric non-member form is often the only one that will do.