When you cannot call hidden friends in C++
What is the main issue?
Let’s see an example where classes and functions are inside a namespace:
namespace ns{
struct foo{
foo(int i){} // NOTE: not explicit
friend void fun1(const foo&){}
friend void fun2(const bar&);
};
void fun2(const bar&){}
}
int main(){
fun1(ns::foo(42));
ns::fun1(ns::foo(42)); // does not compile
fun2(ns::bar(42));
ns::fun2(ns::bar(42));
}
Notice how fun2
can be called with and without the prefix ns::
, but fun1
only without ns::
.
Why is this an issue?
Consider the following example, and all my attempts to call the hidden friend function bar
:
namespace ns{
struct foo{
friend void bar(const foo&){}
};
}
namespace spoiler { void bar(); }
void bar2(ns::foo& f){ bar(f); }
auto lambda = [](ns::foo& f_){bar(f_);};
struct foo2{
ns::foo mf;
void bar(){
bar(mf); // (1)
[&mf]{bar(mf);}(); // (2)
[](ns::foo& f_){bar(f_);}(mf); // (3)
bar2(mf); // (4)
lambda(mf); // (5)
ns::bar(mf); // (6)
::bar(mf); // (7)
{using spoiler::bar; bar(mf);}; // (8)
{void bar(const ns::foo&); bar(mf);}; // (9)
{void bar(); bar(mf);}(); // (10)
}
};
-
Does not compile, compiler tries to call
this->bar
-
Does not compile, compiler tries to call
this->bar
, even if there is nothis
, as it has not been captured -
Does not compile, compiler tries to call
this->bar
, even if there is nothis
, as it has not been captured -
Compiles,
bar2(ns::foo& f)
callsbar(ns::foo& f)
-
Compiles, a lambda in another scope calls calls
bar(ns::foo& f)
-
Does not compile, bar can only be accessed through ADL
-
Does not compile, bar can only be accessed through ADL
-
Compiles, it calls the friend function
bar(const ns::foo& f)
-
Compiles, but will generate a linker error, as there is no
bar(const ns::foo&)
in the right namespace -
Does not compile, the declared
bar()
function does not have the right signature
Those were a lot of attempts, so let’s sum it up.
My issue is that calling the friend function bar
of struct foo;
inside the member function foo2::bar
is not trivial.
First and most important, a little rant.
When writing bar(42)
there is no indication if bar
is a free or member function. This means that also in a sane codebase, one has no idea if bar(42)
could change or not the state of the object. (In a less sane code-base bar(42)
would be a free function and change the state of this
through a global variable) I do not like it, and I wish that C++ would force you to write this->
when accessing member variables and functions.
Second; as it is possible to use the same syntax for calling member and free functions, the compiler has no way to distinguish if bar(this->mf);
should call the member or free function, and the current rules (not sure which) determines that the member functions should be preferred. So the code does not compile.
The second and third examples do not compile, because even if this
is not captured, the scope of the lambda is the scope that contains it.
This explains why lambda
behaves as bar2
, which works as in that context there is no member function called bar
. Unfortunately bar2
(and lambda
) has a big drawback.
One needs to define another function.
If ns::foo
would be a templated class, bar2
would probably need to be templated too and put in the header file too, making it accessible to anyone. Which means leaking an implementation detail of foo2
.
Also writing the function signature might not be that easy, especially for templated functions. In fact, in my example, I made an error (forgot a const
), even if in this case it did not make a relevant difference.
There is also perfect forwarding to consider, paying attention not to make unnecessary copies, avoiding implicit conversion, preserving references, …
Trying to call bar
directly by writing ::bar
and ns::bar
does not work, because hidden friends can be accessed only through ADL.
Using using spoiler::bar;
is a neat trick. spoiler::bar
does not have the correct function signature, nor does it need to have it. By importing the namespace, ADL will be considered again because the lookup set does not contain member functions. Similarly to bar2
it is not a local solution, but definitively a better one. It is much more robust, as one does not need to change the signature of spoiler::bar
if the signature of bar(const foo&)
changes. As the function is not used (one does not even need to implement it), there is also no risk of making additional copies or conversions.
A local alternative to using spoiler::bar;
is to declare the correct function locally. This is better than bar2
as we only need a declaration, and better than spoiler::bar
as this can be done locally.
But contrary to spoiler::bar
, one needs to write the correct function signature, otherwise, the compiler (or linker, as shown in the last two examples) will complain.
Note 📝 | It is not possible to declare a function from another namespace inside a function, otherwise it would be possible to use using spoiler::bar; locally, without leaking the implementation. |
Another possible workaround
There is another possible workaround, but I would not recommend it:
namespace ns{
struct foo{
friend void bar(const foo&){}
};
}
template <class T>
struct foobase{
void bar(){
static_cast<T*>(this)->runbar();
}
};
template <class X>
struct foo2X : foobase<foo2X<X>> {
ns::foo f;
private:
friend foobase<foo2X<X>>;
void runbar(){
bar(this->f);
}
};
using foo2 = foo2X<void>;
Adding a templated indirection to a base class brings bar(const foo&)
in scope again.
Note that without the apparently unnecessary template, it won’t work:
namespace ns{
struct foo{
friend void bar(const foo&){}
};
}
template <class T>
struct foobase{
void bar(){
static_cast<T*>(this)->runbar();
}
};
// no template!
struct foo2 : foobase<foo2> {
ns::foo f;
private:
friend foobase<foo2X>;
void runbar(){
bar(this->f); // does not compile, tries to call this->bar
}
};
Also note that using a template but no base class is not enough
namespace ns{
struct foo{
friend void bar(const foo&){}
};
}
// no base class!
template <class X>
struct foo2X {
ns::foo f;
private:
void bar(){
bar(this->f); // does not compile, tries to call this->bar
}
};
using foo2 = foo2X<void>;
Trying to make the base class private, and use using
for making the bar
member function public, won’t work
namespace ns{
struct foo{
friend void bar(const foo&){}
};
}
template <class T>
struct foobase{
void bar(){
static_cast<T*>(this)->runbar();
}
};
template <class X>
struct foo2X : private foobase<foo2X<X>> {
ns::foo f;
friend foobase<foo2X<X>>;
void runbar(){
bar(this->f);
}
using foobase<foo2X<X>>::bar; // brings bar in scope and triggers error inside runbar
};
using foo2 = foo2X<void>;
The only upside of this convoluted technique is that even if it is not an ABI-compatible change, it is API compatible, as users of foo2
do not need to change any code thanks to using foo2 = foo2X<void>;
. But if this wouldn’t be the case, the easiest solution would have been to rename foo2::bar
to something else.
How big are the chances of encountering this issue?
Note that the same issue when calling from different function
namespace ns{
struct foo{
friend void bar(const foo&){}
};
}
struct foo2{
ns::foo mf;
void bar();
void bar2(){
bar(mf); // Tries to call this->bar
}
};
So this issue is not only present if bar
is called in a bar
member function but if a bar
member function exists at all.
This also means that a base class can break subclasses by adding an unused member function. It does not even need to be virtual
.
To add insult to injury, the function of the base class could also be private
. In this case, GCC and Clang will complain twice:
-
the member function is private and you cannot access it
-
the member function does not have the correct signature
Last but not least, calling a hidden friend function with the same name is a common operation, consider for example:
namespace ns{
struct foo{
friend bool operator==(const foo&, int);
};
}
struct foo2{
ns::foo mf;
bool operator==(int i){
return mf == i;
}
};
But contrary to the example with the function bar
, in this case, the code compiles and does the right thing™! 😲
This is because operators have a separate rule; the presence of the member operator doesn’t stop the search for other candidates as it does for non-operator functions.
This means that in practice, as hidden friend functions (or friend functions) are not used that much except for operators, the chances of encountering such problematic cases are relatively low.
Some guidelines
Hidden friend functions, compared to free, inline
, and friend functions, can help to decrease compilation times and avoid accidental/implicit conversions.
Those are good things, but the fact that the hidden friend functions are only callable through ADL poses an unnecessary limitation because there is no easy way to tell the compiler to consider those functions.
At least there are workarounds to this issue, but it is hard to recognize the issue and find an appropriate solution.
So it might be a good idea to try to define some guidelines for avoiding common errors or subpar design decisions.
First, I think it is useful, to sum up in a table the properties of different functions types
non-friend | non-hidden friend | hidden friend | member | |
---|---|---|---|---|
accessible with ADL | yes | yes | yes | - |
accessible without ADL | yes | yes | no | - |
possible implicit conversion | yes | yes | no | no |
access to private/protected data | no | yes | yes | yes |
needs to be inline | no | no | yes | no |
If you are not the author of the class
If you are not the author of a class, you cannot add (hidden) friend functions or member functions, thus use a free function. You might be able to subclass and add further member functions, but except the class is designed for such scenarios (through virtual
functions or protected
member variables) or it is a particular use-case (which is out of scope for a general guideline), just don’t do it.
If the class is implicitly convertible to other types, and you would like to avoid those conversions when using your functions, consider deleting all other overloads with = delete
.
This technique also works for builtin-types:
void foo(int);
template <class T>
void foo(T) = delete;
foo(42); // OK
foo(std::vector<int>().size()); // compile-error
If you are the author of the class
In this case, it is useful to distinguish between operators and non-operators, since the described issue is not relevant for operators. There is, as far as I know, no particular drawback to using a hidden friend operator function.
Operators
If you need to implement an operator, like operator==
, using a hidden member function can avoid accidental conversions. Supposing that the implementation is trivial (just compare data), the chances of accessing by "accident" private data the functions should be minimal, and thus the drawback negligible.
If the implementation is not trivial, consider not defining operator==
and prefer a named function.
Users expect operator==
to be trivial, not to mutate any data, noexcept
, efficient, and have other properties.
I would not define operator==
as a hidden friend if it is possible or sensible to hide the implementation details. For example, an implementation could call an algorithm on a member variable. Moving the implementation to the corresponding .cpp
file helps to avoid including headers like <algorithm>
, <ranges>
, <utility>
, or iterator
in the header file.
A more realistic example would be implementing operator<<
.
Hiding the implementation in the .cpp
file generally helps to reduce compilation times; the <iostream>
header is considered heavy, even if unused. It also brings in scope and initializes different global variables (std::cout
, std::cerr
and std::clog
), so there is a trade-off between the benefits and possible increase in compilation times and increase in code-bloat.
Notice that for the end-user of a class, it does not matter if operators are implemented as (hidden) friend, member, or free operators, as the syntax is the same, contrary to all other function types.
Non-operators
One of the main criteria for choosing between a member and a non-member function should be having a consistent interface with other "similar" types.
For example, when writing a container-like or string-like class, one should prefer member functions like size
, begin
, end
instead of free or member functions with other names but morally equivalent functionality (like isEmpty
, iterator
, …), even if those functions do not necessarily need to access any private data.
It makes the class easier to use, not only in templated code but also for other people that use C++ and the standard library.
Another important thing to consider should be readability. In particular when multiple parameters of the same class interact; a common error could be switching the parameters. This also happens when parameters are of different types, but easy to convert one to each other, like in the case for memset
.
Given, for example, a class mystring
, I believe that mystring("data").contains("ta")
is for most people easier to type and write than contains(mystring("data"), "ta")
, because it matches better how we would make the same question in English (which one need more or less to know for programming).
I do not like that std::string
acquired in C++23 this new member function (plus overloads), as it already has a lot of member functions. However, I agree that the alternative and existing solutions are harder to read and write.
When writing a more complex algorithm, making it a non-member function like other algorithms should be the way to go.
Especially when using non-member functions, prefer to write explicit conversion operators and constructors unless there is a valid design decision for doing otherwise. Some examples with an implicit constructor by design would be string_view
, span
, or map_view
, because the main use-cases for those types are to be used as cheap parameters to be passed by value.
Note that even if a class only has member functions that define its API, other people will write functions taking it as an argument to add new functionalities, thus one should avoid implicit conversion for avoiding headaches.
Generally, I tend to prefer non-member functions as they improve code reusability and give greater flexibility. It is generally easier to use free functions as callbacks, for example, an ordering function can be used directly in the standard library, like in
std::sort(v.begin(), v.end(), compare);
while an ordering member function needs to be wrapped in a lambda, like in
std::sort(v.begin(), v.end(), [](const auto& e1, const auto& e2){return e1.compare(e2);})
non-member functions also make it easier to define symmetric operators when working with different types.
A typical example is operator==
or a comparator, but the same also holds for functions that can take more than two arguments.
Also in my opinion public member function should call private free functions, even if it is not a technique that it is always possible to use (hey, I’ve just argued that defining a separate function bar2
has drawbacks!).
If the decision is to not use a member function:
-
if there is some benefit in hiding some implementation details, one should avoid hidden friend functions, as they need to be inline
-
if one needs to access private data, then one cannot use a free function
-
if there are a lot of overloads (think about
operator<<
) one could consider a hidden friend -
if the name is very common/there are a lot of overloads, avoid hidden friends (think about the example with all workarounds, you do not want to use such hacks more than once in a shared codebase…)
-
if you do not need access to private data, avoid friend functions
-
for templated classes, hidden friends are generally easier to write and read
Conclusion
The guidelines are unfortunately very generic, and sometimes contradictory. The main takeaway is that hidden friends can be problematic, so I am more cautious using them.
operator==
In most cases, it’s a hidden friend, even if it does not need to access private data.
The reasons are
-
in most cases, it is only comparing internal values, thus the implementation is "small", not many implementation details to hide
-
it is an operator, thus it can be used in any other function without a workaround
-
The implementation is often small, maybe a one-liner, making it a non-hidden friend requires writing in proportion much more code (maybe seven lines of code instead of three, so more than double the size of code for no benefit)
In the extraordinary case that the implementation is not templated and requires calling some other function that is not another operator==
, for example, std::equal
, then I might prefer hiding the implementation in a .cpp
file and using either a free function (if I do not need access to some implementation detail of the class) or a friend function (if I need access to some implementation details).
For most other operators (operator+
, operator-
, …) similar considerations hold.
operator<<
If possible, the implementation is hidden so that there is no need to include the <iostream>
header, but only <iosfwd>
If it needs to access private data, it is either a friend function or a member function. If it does not need to access private data, it’s a free function.
Container classes should have a member size function
Even if it does not need to access private data (like for a compile-time string class). The reasons are that
-
other containers in the standard library have a
size
member function. -
if one wants to use a free function, it can use
std::size
Using a free function or a different named function would only make it harder to use the container class.
mutexed_obj
and lock
"lock" is a generic word, it is also used in the standard library.
I preferred to use a non-member function for symmetry. I wanted to be able to write something like
lock(obj1, obj2, [](const auto& var1, const auto& var2){ /* ... */ });
instead of
obj1.lock(obj2, [](const auto& var1, const auto& var2){ /* ... */ });
as obj1
is not "special" compared to obj2
(unlike std::string::contains
).
This rules the member function out.
I also needed access to the private data, this rules a free function out.
My first implementation supported only one object and planned to expand it in the future. So I preferred to use a hidden friend as long as lock
supported only one mutexed_obj
because
-
it’s templated code, there is nothing that can be moved out from the header file
-
writing an inline function was easier (to read, maintain, and implement)
-
the implementation is short, using a non-inline function duplicates the amount of code, and it’s simpler to write in inline
Considering that it inhibits to use easily this class inside a class that has a lock member function (it’s because of this function that I’ve had so many thoughts lately about friend functions), I should have used a non-hidden friend function from the start.
Do you want to share your opinion? Or is there an error, some parts that are not clear enough?
You can contact me anytime.