Hidden Friends
I describe what argument dependent lookup is. I show how it ensures the compilation of even most simple C++ programs. Then, I go over its disadvantages and how it can be used more efficiently through the hidden friends pattern.
Argument Dependent Lookup
Before understanding the concept of hidden friends, we need to understand what argument dependent lookup (ADL) is. It is also called Koenig-Lookup and its purpose is simplification of code.
Consider the following. Including the whole namespace std
is considered
bad practice. By using namespace std
, you make all the types visible in
your module. It might be not what you want, as std::swap()
or
std::vector
might introduce name conflicts with other implementations.
Instead, you might decide to pick specific types from std
.
#include <iostream> auto main() -> int { std::cout << "Poor man's string.\n"; }
This is a trivial example, but it demonstrates ADL already. Why does it
compile? Sure, std::cout
is visible, but we also use operator <<
on
it. std::cout
is the only type we explicitly take from std
. How is the
operator found without using std::operator <<
?
The solution is ADL. It is specified in §6.5.3 of the current C++ specification as part of the name lookup rules.
C++ Specification §6.5.3/1
When the postfix-expression in a function call (7.6.1.3) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (6.5.2) may be searched, and in those namespaces, namespace-scope friend function or function template declarations (11.9.4) not otherwise visible may be found. These modifications to the search depend on the types of the arguments (and for template template arguments, the namespace of the template argument).
The following sections clarify details. ADL extends searchable namespaces for
the function's name to those of the function's parameters. In our example, the
function is operator <<
. The second parameter is a built-it type.
Therefore, it does not introduce any new namespaces. The first does, however.
Because of std::cout
the namespace std
is also searched for
operator <<
candidates. The fitting one taking a character literal is
found among those. This is the reason why the above example compiles.
Precondition.
For ADL to be allowed, the function's name must be unqualified. Hence, only
a simple name triggers it. An example would be swap()
, while
std::swap()
would prevent ADL due to the specified namespace. Even
parentheses would do that.
In the example above, we could have written our call using function syntax.
operator << (std::cout, "Poor man's string.\n");
This compiles fine. The operator name in parenthesis would disable ADL,
however. In such case we would need to pull it explicitly from std
.
/* Option 1: Explicit specification of namespace. */ (std::operator <<)(std::cout, "Poor man's string.\n"); /* Option 2: Explicit usage of operator. */ using std::operator <<; (operator <<)(std::cout, "Poor man's string.\n");
In conclusion, ADL makes your life easier. It allows the compiler to implicitly look for functions in places that usually make sense.
ADL
ADL extends visible namespaces to those of the function's parameters. The function's name needs to be unqualified.
Disadvantages of ADL
ADL works in most situations. However, it also yields too many candidates most of the time. Consider the following prominent example.
#include <iostream> #include <vector> auto main() -> int { std::vector v{0}; std::cout << v; }
If you try to compile that, you instantly get an email. It is your compiler
asking for a long meeting. It wants to go over all the myriads of candidates
for operator <<
and explain very precisely why each of them does not fit.
You and your compiler both know the reason. There is no operator <<
which
takes std::vector
as parameter. It does not save you from compiler's
monologue, however. The compiler needs to regard all candidates equally. There
is no way for it to know which candidates are important and which are not. The
only way would be to limit the number of candidates in the first place.
Worse scenarios than overly long error messages exist. Among candidates provided by ADL some might unintentionally fit. If your type can be implicitly converted to match such a candidate, the way is open for some debugging sessions.
Conclusion
ADL extends the search for function candidates to its parameters' scopes. Especially for the standard library this leads to overly long error messages. Moreover, even unintended candidates might be pulled in.
You cannot change STL's specification to apply the hidden friends pattern. However, you can apply the pattern to your own code. This avoids surprises and provides less verbose compiler messages.
Have fun with it and write solid code.