Argument-dependent lookup, also known as ADL, or Koenig lookup, is the set of rules for looking up the unqualified function names in function-call expressions, including implicit function calls to overloaded operators. These function names are looked up in the namespaces of their arguments in addition to the scopes and namespaces considered by the usual unqualified name lookup.
Argument-dependent lookup makes it possible to use operators defined in a different namespace.
#include <iostream> int main() { std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL // examines std namespace because the left argument is in // std and finds std::operator<<(std::ostream&, const char*) operator<<(std::cout, "Test\n"); // same, using function call notation // however, std::cout << endl; // Error: 'endl' is not declared in this namespace. // This is not a function call to endl(), so ADL does not apply endl(std::cout); // OK: this is a function call: ADL examines std namespace // because the argument of endl is in std, and finds std::endl (endl)(std::cout); // Error: 'endl' is not declared in this namespace. // The sub-expression (endl) is not a function call expression }
Details
First, if the lookup set produced by usual unqualified lookup contains any of the following:
Then the argument-dependent lookup is not considered.
Otherwise, for every argument in a function call expression and for every template template argument of a template function, its type is examined to determine the associated set of namespaces and classes that it will add to the lookup.
If any namespace in the associated set of classes and namespaces is an inline namespace, its enclosing namespace is also added to the set.
If any namespace in the associated set of classes and namespaces directly contains an inline namespace, that inline namespace is added to the set.
After the associated set of classes and namespaces is determined, all declarations found in classes of this set are discarded for the purpose of further ADL processing, except namespace-scoped friend functions and function templates, as stated in point 2 below .
The set of declarations found by ordinary unqualified lookup and the set of declarations found in all elements of the associated set produced by ADL, are merged, with the following special rules.
Notes
Because of argument-dependent lookup, non-member functions and non-member operators defined in the same namespace as a class are considered part of the public interface of that class (if they are found through ADL) [1]. ADL is the reason behind the established idiom for swapping two objects in generic code:
using std::swap; swap(obj1, obj2);
because calling std::swap(obj1, obj2)
directly would not consider the user-defined swap() functions that could be defined in the same namespace as the types of obj1 or obj2, and just calling the unqualified swap(obj1, obj2)
would call nothing if no user-defined overload was provided. In particular, std::iter_swap
and all other standard library algorithms use this approach when dealing with Swappable
types.
Name lookup rules make it impractical to declare operators in global or user-defined namespace that operate on types from the std namespace, e.g. a custom operator>>
or operator+
for std::vector
or for std::pair
(unless the element types of the vector/pair are user-defined types, which would add their namespace to ADL). Such operators would not be looked up from template instantiations, such as the standard library algorithms. See dependent names for further details.
ADL can find a friend function (typically, an overloaded operator) that is defined entirely within a class or class template, even if it was never declared at namespace level.
template<typename T> struct number { number(int); friend number gcd(number x, number y) { return 0; }; // definition within // a class template }; // unless a matching declaration is provided gcd is an invisible (except through ADL) // member of this namespace void g() { number<double> a(3), b(4); a = gcd(a,b); // finds gcd because number<double> is an associated class, // making gcd visible in its namespace (global scope) // b = gcd(3,4); // Error; gcd is not visible }
Although a function call can be resolved through ADL even if ordinary lookup finds nothing, a function call to a function template with explicitly-specified template arguments requires that there is a declaration of the template found by ordinary lookup (otherwise, it is a syntax error to encounter an unknown name followed by a less-than character).
namespace N1 { struct S {}; template<int X> void f(S); } namespace N2 { template<class T> void f(T t); } void g(N1::S s) { f<3>(s); // Syntax error (unqualified lookup finds no f) N1::f<3>(s); // OK, qualified lookup finds the template 'f' N2::f<3>(s); // Error: N2::f does not take a non-type parameter // N1::f is not looked up because ADL only works // with unqualified names using N2::f; f<3>(s); // OK: Unqualified lookup now finds N2::f // then ADL kicks in because this name is unqualified // and finds N1::f }
There are two contexts where ADL-only lookup (that is, lookup in associated namespaces only) takes place:
- the lookup of non-member functions
begin
andend
performed by the range-for loop if member lookup fails - the dependent name lookup from the point of template instantiation.
Examples
Example from http://www.gotw.ca/gotw/030.htm.
namespace A { struct X; struct Y; void f(int); void g(X); } namespace B { void f(int i) { f(i); // calls B::f (endless recursion) } void g(A::X x) { g(x); // Error: ambiguous between B::g (ordinary lookup) // and A::g (argument-dependent lookup) } void h(A::Y y) { h(y); // calls B::h (endless recursion): ADL examines the A namespace // but finds no A::h, so only B::h from ordinary lookup is used } }
See also
References
- H. Sutter (1998) "What's In a Class? - The Interface Principle" in C++ Report, 10(3)
Please login to continue.