ADL is the abbreviation for Argument-dependent lookup
, which in Chinese translates to 参数依赖查找. ADL is useful for avoiding verbose code, but it can also cause some ambiguity.
If a function definition cannot be found in the context where the function is used, we can look for it in the namespace of its parameters. This means that when a function is called, even if its declaration is not in the current scope, as long as it is declared in the namespace of some actual parameter, the compiler can find it.
1 | namespace Chrono{ |
When a class member calls a named function, the compiler will prioritize other members of the same class and its base classes rather than functions found based on parameter types (Note: operators are based on different rules (argument matching)).
Let X be the lookup set produced by unqualified lookup (3.4.1) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains:
- a declaration of a class member, or
- a block-scope function declaration that is not a using-declaration, or
- a declaration that is neither a function nor a function template
then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the argument types as described below. The set of declarations found by the lookup of the name is the union of X and Y. [ Note: The namespaces and classes associated with the argument types can include namespaces and classes already considered by the ordinary unqualified lookup.
1 | namespace NS { |
For more information about ADL, refer to ISO/IEC 14882:2014(E) §3.4.2 P47
, or you can view it directly here.
Now we will discuss another situation where we do not want to resort to ADL and the ambiguity it causes.
Consider the following situation:
1 | namespace A{ |
In fact, the code above will result in a compilation error (error: call to 'copy' is ambiguous
).
Due to ADL, B::algo also found the fully matching function A::copy in its parameter’s namespace (A), causing ambiguity.
When writing code, be sure to pay attention to such situations. It is best to use using
or ::
to explicitly specify which version we want to call when invoking functions with actual parameters from different namespaces within the function definition.
1 | void algo(A::base& x,A::base& y){ |
Especially when using unconstrained template combinations, because we do not know whether the incoming template argument has the function we currently need in its namespace, it is essential to maintain good coding habits.
1 | void g(int){ |
In the code above, the g
called from the member function f
of the object B<A>
is ::g(int)
, not the inherited A::g(char)
from A
, because g(2)
does not depend on the template parameter T
, therefore it binds at the point of definition. The name from the template argument T
(which just happens to be used as a base class) has yet to be brought to the compiler’s attention at this point, thus it will not be considered.
Only at the instantiation point can we determine whether the parameter T
has the required name.
Note the difference between
instantiation point binding
anddefinition point binding
.
- Instantiation point binding: The context necessary to determine the meaning of a dependent name is provided by the use of the template (given a set of actual parameters). For a template class or a class member, the instantiation point is exactly located before its usage declaration. But to allow for recursive calls, the instantiation point of a function template is located after the declaration that instantiates it.
- Definition point binding: The compiler treats names that do not depend on template arguments as names outside the template. Therefore, these names must be in scope at the point of definition.
If we want a name from a dependent context to be considered, we must make the dependency explicit. There are three ways to accomplish this:
- Qualify the name with the dependent type (T::g)
- Declare a name pointing to an object of this class (this->g)
- Use a using declaration to bring the name into scope (using T::g)
To disable ADL, you can use the following two methods:
- Explicitly specify using
::
(qualifying the callnamespaceName::funcName()
) - Place the function name in parentheses (
(funcName)()
)
1 | namespace TEST{ |