The C++ logo, by Jeremy Kratz, licensed under CC0 1.0 Universal

How to write friend functions

Notes published the
14 - 17 minutes to read, 3468 words
Categories: c++
Keywords: ADL c++ conversion friend implementation detail inline member function private

These notes are about how to write (and how not to write) friend functions and how they work, not so much about why and when you should or would want to use them.

Simple cases are simple, but how friend functions interact with each other and what a specific syntax means is…​ not always easy to understand.

Important !!
No friends were harmed in the making of these notes; on the contrary, some new friends were made /s

Example of a friend function

In C++, similarly to other object-oriented languages, it is possible to assign specific visibility to its member functions and variables: public, protected, and private.

Normally code that lives outside of the class (ignoring inheritance for simplicity) can access directly only public member variables.

It is possible to grant functions and other classes that live outside of the class access to the private and protected member variables and functions, by marking them as friend.

A minimal example would look like

struct foo {
	explicit foo(int i): i(i){}
	friend int bar(const foo&);
	private:
	int i;
};

int bar(const foo& f){
	return f.i; // (1)
}

int baz(const foo& f){
	return f.i; // (2)
}
  1. Can access private member variables without triggering compiler error

  2. Compiler error, cannot access the private member variable

Friend functions are not strictly necessary. One can replace them with member variables, but there are use cases for which friend functions are really useful, the most obvious one is operator==.

Thanks to member functions, it is possible to make operator== symmetric when comparing a given class with other types.

Consider following classes

struct foo {
	explicit foo(int i): i(i){}
	friend bool operator==(const foo&, const foo&);
	friend bool operator==(const foo&, int);
	friend bool operator==(int, const foo&);
	private:
	int i;
};

bool operator==(const foo& l, const foo& r){
	return l.i == r.i;
}
bool operator==(const foo& l, int i){
	return l.i == i;
}
bool operator==(int i, const foo& r){
	return i == r.i;
}

struct bar{
	explicit bar(int i): i(i){}
	bool operator==(const bar& r) const {
		return this->i == r.i;
	}
	bool operator==(int i) const {
		return this->i == i;
	}
	private:
	int i;
};

int main(){
	auto f1 = foo(42);
	auto f2 = foo(0);
	f1 == 0;
	0 == f1;


	auto b1 = bar(42);
	auto b2 = bar(0);
	b1 == 0;
	0 == b1; // this line fails to compile
}

There are other interesting and useful use cases, but those notes are about how to write friend function, not why one would want to.

Hidden friend functions

A hidden friend function looks like this:

struct foo {
	foo(int i): i(i){}
	int friend bar(const foo& f){
		return f.i;
	}
	private:
	int i;
};

If you are wondering if there are other differences to the minimal friend function exmaple, except for bar being implemented inline: there are no other differences.

Hidden friend functions are inline friend functions.

Note 📝
Most examples will have a hidden friend and a friend function to show behavioral differences.

But why do they have a different name? (note: it’s not a name that comes from the C++ standard)

It’s because they have slightly different properties.

Hidden friend and implicit conversions

The first difference between a friend and a hidden friend function, and possibly most important, is that a hidden friend function is not taken into account by the compiler unless the parameter is exactly of type foo.

An example:

struct foo {
	foo(int i): i(i){}
	friend int hidden_friend_fun(const foo& f){
		return f.i;
	}
	friend int friend_fun(const foo& f);
	private:
	int i;
};

int friend_fun(const foo& f){
	return f.i;
}

int main(){
	hidden_friend_fun(42); // does not compile
	hidden_friend_fun(foo(42));
	friend_fun(42);
	friend_fun(foo(42));
}

If someone tries to compile this example, it will notice that the line hidden_friend_fun(42) does not compile, even if 42 is convertible to foo. In fact, friend_fun(42) compiles because it considers the implicit conversion. hidden_friend_fun and friend_fun seem to be equivalent, but in fact, they are not. If the constructor had been explicit, as in the next example, also friend_fun(42) would not compile.

struct foo {
	explicit foo(int i): i(i){}
	int friend hidden_friend_fun(const foo& f){
		return f.i;
	}
	int friend friend_fun(const foo& f){
		return f.i;
	}
	private:
	int i;
};

int main(){
	hidden_friend_fun(42); // does not compile
	hidden_friend_fun(foo(42));
	friend_fun(42); // does not compile
	friend_fun(foo(42));
}

then also friend_fun(42) does not compile.

Hidden friends and ADL

The second difference between a friend and a hidden friend function is that a hidden friend function can only be called through ADL; one does never specify the namespace where the hidden friend function is declared.

namespace ns{
	struct foo {
		friend int bar(const foo& f, int j) {
			return f.i + j;
		}
		private:
		int i = 42;
	};
}
int main() {
	auto f = ns::foo();
	bar(f, 42);
	::bar(f, 42);   // does not compile
	ns::bar(f, 42); // does not compile
}

TL;DR: when using hidden friend, one does not write the namespace.

This is an issue for a function that would not take the befriended class as a parameter:

namespace ns{
	struct foo {
		friend int bar(int j) {
			return j;
		}
		private:
		int i = 42;
	};
}
int main() {
	bar(42);	 // does not compile
	::bar(42);   // does not compile
	ns::bar(42); // does not compile
}

The only valid reason I can see for bar being a friend without taking a foo as a parameter is because it needs to access the private data of a global foo instance (which is not the case in this example). If not, then it does not need to be a friend function at all.

It is possible to bring bar in scope by adding a declaration:

namespace ns{
	struct foo {
		friend int bar(int j) {
			return j;
		}
		private:
		int i = 42;
	};
	int bar(int j);
}

int main() {
	bar(42);	 // does not compile
	::bar(42);   // does not compile
	ns::bar(42);
}

I do not think there are relevant use cases for a hidden friend function that does not take the befriended class as a parameter, the corner case for accessing globals can be rewritten in another way.

Friend member functions

A member function of a class can be a friend function of another class

struct S1;

struct S2 {
	int foo(const S1& s1);
};

struct S1 {
	friend int S2::foo(const S1& s1);
	S1(int i) : i(i){}
	private:
	int i;
};

int S2::foo(const S1& s1) {
	return s1.i;
}

int main() {
	S2().foo(42);
	S2().foo(S1(42));
}

Note that it is not possible to declare a hidden member friend function, for example by writing

struct S1;

struct S2 {
	int foo(const S1& s1);
};

struct S1 {
	friend int S2::foo(const S1& s1){
		return s1.i;
	}
	S1(int i) : i(i){}
	private:
	int i;
};

triggers the compiler error error: cannot define member function 'S2::foo' within 'S1' in GCC; Clang and MSVC have slightly different error messages, but the root cause is the same.

This means that it is not possible to disallow S2().foo(42); and have an implicit S1 constructor.

Multiple classes with a common friend function

struct S1;

struct S2 {
	friend int foo(const S2& s2, const S1& s1);
	S2(int i) : i(i){}
	private:
	int i;
};

struct S1 {
	friend int foo(const S2& s2, const S1& s1);
	S1(int i) : i(i){}
	private:
	int i;
};

int foo(const S2& s2, const S1& s1){
	return s2.i + s1.i;
}

int main() {
	foo(0, 42);
	foo(S2(0), 42);
	foo(S2(0), S1(42));
}

works as expected, no surprises.

Multiple classes and a hidden friend function

It is not possible to define a hidden friend function for multiple classes (you’ll get a redefinition error), but it is possible to make a friend function for multiple classes which is also a hidden friend for one class:

struct S1;

struct S2 {
	friend int foo(const S2& s2, const S1& s1);
	S2(int i) : i(i){}
	private:
	int i;
};

struct S1 {
	friend int foo(const S2& s2, const S1& s1){
		return s2.i + s1.i;
	}
	S1(int i) : i(i){}
	private:
	int i;
};


int main() {
	foo(0, 42); // does not compile because hidden friend of S2
	foo(S2(0), 42);
	foo(S2(0), S1(42));
}

Templated friend function in non-templated class

A non-templated class and a templated friend function

struct foo {
	foo(int i): i(i){}

	template <class J>
	friend J hidden_friend_fun(const foo& f, J j){
		return f.i + j;
	}
	template <class J>
	friend J friend_fun(const foo& f, J j);

	private:
	int i;
};

template <typename J>
J friend_fun(const foo& f, J j){
	return f.i + j;
}

int main(){
	hidden_friend_fun(foo(42), 0ul);
	friend_fun(42, 0ul);
	friend_fun(foo(42), 0ul);
}

In this case, all (templated) friend functions are befriended with a given structure, this is the opposite of multiple classes with a common friend function.

Templated class with friend function

Templated class with friend function (no befriended parameter)

template <typename I>
struct foo {
	foo(I i): i(i){}

	friend int friend_fun(int j);
	private:
	I i;
};

const foo<long>	 global1 = foo<long>(42l);
const foo<unsigned> global2 = foo<unsigned>(42u);

int friend_fun(int j){
	return global1.i + global2.i + j;
}

int main(){
	friend_fun(-1);
}

This example shows that we have one friend function that can access the private data of multiple template classes.

In this case, if the hidden friend function does not depend on foo, it will trigger redefinition errors:

template <typename I>
struct foo;

extern const foo<long> global1;
extern const foo<unsigned> global2;

template <typename I>
struct foo {
	foo(I i): i(i){}

	friend int hidden_friend_fun(){
		return global1.i + global2.i;
	}
	private:
	I i;
};
int hidden_friend_fun();

const foo<long> global1 = foo<long>(42l);
const foo<unsigned> global2 = foo<unsigned>(42u); // leads to a redefintion of hidden_friend_fun and compiler error

int main(){
	hidden_friend_fun();
}

which does support my thesis that hidden friends that do not take the befriended class as a parameter cause more harm than good.

Templated class with friend function (with foo as a parameter)

Now things become a little more interesting.

template <typename I>
struct foo;

extern const foo<unsigned> global;

template <typename I>
struct foo {
	foo(I i): i(i){}

	friend I hidden_friend_fun(const foo& f){
		return global.i + f.i;
	}
	friend I friend_fun(const foo& f);

	private:
	I i;
};

const foo<unsigned> global = foo<unsigned>(-1);

int friend_fun(const foo<int>& f){
	return global.i + f.i; // (1)
}
unsigned friend_fun(const foo<unsigned>& f){
	return global.i + f.i; // (2)
}

int main(){
	hidden_friend_fun(foo(42));
	friend_fun(foo(42));
	friend_fun(foo(42l)); // (3)
}
  1. compiler error; int friend_fun(const foo<int>&) has not access to foo<unsigned>::i, it is private in this context

  2. unsigned friend_fun(const foo<unsigned>&) can access foo<unsigned>::i

  3. compiler error; there is no definition of long friend_fun(const foo<long>&)', only a declaration

Adding foo as a parameter has multiple effects.

The first is that we do not have a single friend_fun function anymore, but an overload for every instantiation. Note that friend_fun itself is not a template function.

hidden_friend_fun is also not a templated function, but it is still a friend with all instantiations of foo. And there are no redefinition errors.

Note 📝
It is possible to add the template parameter to the declaration, as in friend I hidden_friend_fun(const foo<I>& f, int i){return f.i + i;} and friend I friend_fun(const foo<I>& f, int i);. It does not make any difference. As those are not templated functions, I think I prefer to avoid writing the template parameter to avoid confusion.

Templated class with friend member function

What is interesting in the previous example is that we declared implicitly an overload set of functions.

This works the same way for a friend member function

template <typename I>
struct foo;

struct S2{
	int bar(const foo<int>& f);
	unsigned bar(const foo<unsigned>& f);
};

extern const foo<unsigned> global;

template <typename I>
struct foo {
	foo(I i): i(i){}

	friend I S2::bar(const foo& f);

	private:
	I i;
};

const foo<unsigned> global = foo<unsigned>(-1);

int S2::bar(const foo<int>& f){
	return global.i + f.i; // (1)
}
unsigned S2::bar(const foo<unsigned>& f){
	return global.i + f.i; // (2)
}

int main(){
	S2 s2;
	s2.bar(foo(42));
	s2.bar(foo(42u));
	s2.bar(foo(42l)); // (3)
}
  1. compiler error; int S2::bar(const foo<int>&) has not access to foo<unsigned>::i, it is private in this context

  2. unsigned S2::bar(const foo<unsigned>&) can access foo<unsigned>::i

  3. compiler error; there is no declaration of S2::bar(const foo<long>&)'

Thus for the overload taking const foo<long> the error is slightly different. For a free function, there is no definition, but for a member function, there is also no declaration. In practice, it won’t make a big difference as in both cases the fix is the same: add the missing overloads.

The only difference is that one needs to add also the appropriate declaration inside S2, while for non-member functions that declaration is not necessary.

Templated class with friend function (no overloads)

Declaring all overloads by hand is not only counterproductive if the declaration is the same, but also impossible if foo is part of a public API and the end-use should not write the overload by himself.

A better alternative is to have friend_fun being a templated function on the same template parameter of foo:

template<class I> class foo;
template<class I> int friend_fun(const foo<I>&);

extern const foo<unsigned> global;

template<class I> class foo {
	I i;
	public:
	foo(const I& i) : i(i) {}
	friend int friend_fun<>(const foo<I>&);
};

const foo<unsigned> global = foo<unsigned>(-1);

template<class I> int friend_fun(const foo<I>& f) {
	return f.i + global.i;
}

int main() {
	friend_fun(foo(42));  // (1)
	friend_fun(foo(42u)); // (2)
}
  1. int friend_fun(const foo<I>&), with I = int has no access to unsigned foo<unsigned>::i

  2. int friend_fun(const foo<I>&), with I = int has access to unsigned foo<unsigned>::i

Note the declaration: friend int friend_fun<>(const foo<I>&);. The <> denotes that in this case, we have a template function, but this declaration works if the template function is also forward-declared: template<class I> int friend_fun(const foo<I>&);

Templated friend function in a templated class

template <typename I>
struct foo;

extern const foo<unsigned> global;

template <typename I>
struct foo {
	foo(I i): i(i){}

	template <class J>
	friend int hidden_friend_fun(const foo& f, J j){
		return global.i + f.i + j;
	}
	template <class J>
	friend int friend_fun(const foo& f, const J j);

	private:
	I i;
};

const foo<unsigned> global = foo<unsigned>(-1);

template <class J>
int friend_fun(const foo<int>& f, J j){
	return global.i + f.i + j; // (1)
}

int main(){
	hidden_friend_fun(foo(42),0);
	friend_fun(foo(42),0);
}
  1. compiler error; int friend_fun(const foo<int>&) has no access to foo<unsigned>::i, it is private in this context

In this example, both friend_fun and hidden_friend_fun are friends of only one given instance, both cannot access the private data of the global instance. Removing the access to global.i will make the examples compile.

One family of friend functions for all templated classes

To have a friend_fun that can access the private data of every templated instance, one needs to remove foo from the parameters, and replace it with a template parameter:

template<class I> class foo;

extern const foo<unsigned> global;

template<class I>
struct foo {
	foo(I i) : i(i) {}
	template <class F1>
	friend int friend_fun(const F1& f1);
	private:
	I i;
};

const foo<unsigned> global = foo<unsigned>(-1);

template <class F1>
int friend_fun(const F1& f1){
	return f1.i + global.i;
}

int main() {
	friend_fun(foo(42));
	friend_fun(foo(42l));
	friend_fun(foo(42u));
}

doing something similar for the hidden_friend_function, as already mentioned, is generally a bad idea, as it causes redefinition errors

template<class I>
struct foo {
	foo(I i) : i(i) {}
	template <class F1>
	friend int hidden_friend_fun(const F1& f){
		return f.i;
	};
	private:
	I i;
};

const auto f1 = foo<unsigned>(42u);
const auto f2 = foo<long>(42l);

Declare a friend function in another namespace

It is possible to declare a friend function in another namespace

namespace ns1 {
	struct foo;
}
namespace ns2 {
	int friend_function(const ns1::foo&);
}

namespace ns1{
	struct foo {
		foo(int i): i(i){}
		friend int ns2::friend_function(const foo&);
		private:
		int i;
	};
}

namespace ns2 {
	int friend_function(const ns1::foo& f){return f.i;};
}

int main() {
	ns2::friend_function(42);
}

Note that without the forward declaration of ns2::friend_function, the compiler will complain (similarly to member functions).

Note 📝
Also the global namespace counts as another namespace

Hidden friends cannot be declared in another namespace.

Functions in other namespaces can be parsed as member functions

Consider the following example, where the friend function friend_function is in the global namespace and the befriended class in a namespace.

struct bar
{
	int j = 0;
};

namespace ns1 { class foo; }

bar friend_function(const ns1::foo&);

namespace ns1{
	struct foo {
		foo(int i): i(i){}
		friend bar ::friend_function(const foo&);
		private:
		int i;
	};
}

bar friend_function(const ns1::foo& f){
	return bar{f.i};
}

This code won’t compile. If friend_function is changed to return an int instead of bar, then the code compiles and works as expected.

In this case, friend bar ::friend_function(const foo&); is parsed as friend bar::friend_function(const foo&);, as if the class has a friend member function friend_function. As int does not have member functions, there is only one possible way to interpret such a line.

Fortunately, it is possible to disambiguate the situation also for classes by adding a pair of parenthesis:

struct bar
{
	int j = 0;
};

namespace ns1 { class foo; }

bar friend_function(const ns1::foo&);

namespace ns1{
	struct foo {
		foo(int i): i(i){}
		friend bar (::friend_function(const foo&));
		private:
		int i;
	};
}

bar friend_function(const ns1::foo& f){
	return bar{f.i};
}

Conclusion

I think I’ve enlisted all variations I’ve ever seen.

The main takeaway is that hidden friends are generally easier to write, have less flexibility, but generally work as expected.

Non-hidden friends are more customizable (they can be member functions, functions in another namespace, or functions that do not take the befriended class directly as a parameter…​), but sometimes the error messages are really confusing.

In the case of templated class, hidden friends have access to the private data of all instantiations, for non-hidden friends one needs to jump through some hoops.


Do you want to share your opinion? Or is there an error, some parts that are not clear enough?

You can contact me anytime.