C++
What is Argument-Dependent Lookup aka ADL or Koenig Lookup
Have you ever written C++ code that just works, and you’re not entirely sure why? Chances are, you’ve benefited from a powerful (and sometimes mysterious) feature called Argument-Dependent Lookup, often abbreviated as ADL, and also known as Koenig Lookup. This mechanism allows the compiler to find functions in namespaces associated with the arguments passed to the function, even if those functions aren’t explicitly qualified. This can lead to cleaner, more readable code, particularly when dealing with operators and overloaded functions. Understanding ADL is crucial for writing robust and maintainable C++ code, especially when working with libraries and templates. It’s a cornerstone of generic programming and allows for a more intuitive interaction between different parts of your codebase. We’ll demystify this concept and explore its practical applications in detail.
Understanding Argument-Dependent Lookup (ADL)
At its core, Argument-Dependent Lookup (ADL) is a rule the C++ compiler uses to find the correct function to call. Normally, the compiler searches for a function in the current scope and any enclosing scopes. However, ADL extends this search to include the namespaces of the arguments passed to the function. This is particularly useful when working with operators. For example, if you define a custom class and overload the + operator for it, ADL ensures that the compiler can find your overloaded operator even if it’s defined in a different namespace than where you’re using it. This allows for more natural and intuitive syntax when working with custom types.
The primary motivation behind ADL is to make generic programming easier and more intuitive. Without ADL, you would often have to explicitly qualify function calls with their namespace, which can lead to verbose and cluttered code. ADL simplifies this process by automatically searching the relevant namespaces based on the arguments involved. This is particularly important when working with templates, where the types of the arguments might not be known until compile time. ADL ensures that the correct functions are found regardless of the specific types used. According to Herb Sutter, a leading C++ expert, “ADL is a critical feature for enabling generic programming in C++.” [External Link: Herb Sutter’s website - Replace with actual link].
Consider this example. Let’s say you have a namespace MyMath containing a function add that operates on a custom type MyInt. If you call add(MyInt(5), MyInt(3)) from outside the MyMath namespace, without ADL, the compiler would not be able to find the add function. However, with ADL, the compiler will look in the MyMath namespace because MyInt is defined within it, and it will successfully find and call the add function. This automatic lookup drastically reduces the need for explicit namespace qualification and makes the code cleaner and more readable. This showcases the power and convenience of argument dependent lookup.
How Argument-Dependent Lookup Works
The process of Argument-Dependent Lookup involves several steps. First, the compiler identifies the function call that requires resolution. Then, it examines the types of the arguments passed to the function. For each argument type, the compiler determines the associated namespaces. These namespaces are the ones in which the type itself is defined, as well as any namespaces in which its base classes or template arguments are defined. Finally, the compiler searches for a matching function declaration within those namespaces, in addition to the usual scope-based lookup. If a suitable function is found, it is considered a viable candidate for overload resolution.
Here’s a breakdown of the namespaces that ADL considers:
- The namespace in which the argument type is defined.
- For class types, the namespaces in which any base classes are defined.
- For template arguments, the namespaces in which the template arguments themselves are defined.
It’s important to note that ADL only applies to unqualified function calls. If you explicitly qualify a function call with its namespace (e.g., MyMath::add(x, y)), ADL is bypassed, and the compiler only searches in the specified namespace. Also, ADL only considers function declarations that are visible at the point of the function call. This means that the function declaration must be declared before the function call, or it must be declared in a header file that is included before the function call. The visibility rule prevents unexpected behavior and ensures that the compiler only considers functions that are intended to be used in the calling context. [External Link: CPP Reference - Replace with actual link].
Here’s a featured snippet-optimized paragraph: Argument-Dependent Lookup (ADL) is a key feature in C++ that allows the compiler to find functions based on the types of their arguments. Instead of just searching the current and enclosing scopes, ADL extends the search to namespaces associated with the argument types. This is especially useful for operator overloading and generic programming, leading to cleaner and more readable code by reducing the need for explicit namespace qualification. This automatic namespace resolution is a cornerstone of modern C++ programming techniques.
Practical Examples of Argument-Dependent Lookup
Argument-Dependent Lookup shines in several real-world scenarios, particularly in operator overloading and generic programming with templates. Consider the standard std::swap function. When you call swap with two objects of a custom type, ADL allows the compiler to find a specialized swap function defined in the same namespace as your custom type, even if you haven’t explicitly included the
Another common example is working with iterators. Iterators are often used with algorithms from the
Here’s an example using a custom type called Vector2D:
- Define the Vector2D class and the + operator in a namespace Geometry.
- Create instances of Vector2D in your main code.
- Use the + operator to add the Vector2D instances. The compiler uses ADL to find the + operator in the Geometry namespace.
While Argument-Dependent Lookup is a powerful tool, it can also lead to unexpected behavior if not understood properly. One common pitfall is unintended function calls due to ADL finding a function in an unexpected namespace. This can happen if your code relies on unqualified function calls and there are multiple functions with the same name in different namespaces that could potentially be considered by ADL. To avoid this, it’s generally a good practice to be explicit about the namespaces you intend to use, especially when working with common function names.
Another potential issue is related to template argument deduction. ADL can sometimes interfere with template argument deduction, leading to compilation errors or unexpected behavior. In such cases, it’s often necessary to explicitly specify the template arguments or to use qualified function calls to guide the compiler. It’s also important to be aware of the potential for ADL to be disabled in certain contexts, such as when using the using directive. The using directive can introduce names into the current scope, which can then shadow names that would otherwise be found by ADL.
Here are some best practices for using ADL effectively:
- Be mindful of the namespaces associated with your types.
- Use qualified function calls when ambiguity exists.
- Understand how ADL interacts with templates and the using directive.
By following these guidelines, you can leverage the benefits of ADL while avoiding its potential pitfalls. You can also find more information about it from this internal link.
FAQ About Argument-Dependent Lookup
- What is the main purpose of Argument-Dependent Lookup?
- The main purpose is to simplify generic programming by allowing the compiler to find functions based on the types of their arguments, reducing the need for explicit namespace qualification.
- Does ADL work with all function calls?
- No, ADL only applies to unqualified function calls. If you explicitly qualify a function call with its namespace, ADL is bypassed.
- Can ADL cause unexpected behavior?
- Yes, ADL can lead to unexpected function calls if not understood properly, especially when there are multiple functions with the same name in different namespaces.
- Is Argument-Dependent Lookup the same as Koenig Lookup?
- Yes, Argument-Dependent Lookup (ADL) and Koenig Lookup are two names for the same language feature in C++.
Preferably I’d like to know:
- Why is it a good thing?
- Why is it a bad thing?
- How does it work?
Argument Dependent Lookup (ADL), sometimes called Koenig Lookup, describes how unqualified names are looked up by the compiler in C++.
The C++11 standard § 3.4.2/1 states:
When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope friend function declarations (11.3) 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).
In simpler terms Nicolai Josuttis states1:
You don’t have to qualify the namespace for functions if one or more argument types are defined in the namespace of the function.
A simple code example:
namespace MyNamespace { class MyClass {}; void doSomething(MyClass) {} } MyNamespace::MyClass obj; // global object int main() { doSomething(obj); // Works Fine - MyNamespace::doSomething() is called. }
In the above example there is neither a using-declaration nor a using-directive but still the compiler correctly identifies the unqualified name doSomething() as the function declared in namespace MyNamespace by applying ADL.
How does it work?
The algorithm tells the compiler to not just look at local scope, but also the namespaces that contain the argument’s type. Thus, in the above code, the compiler finds that the object obj, which is the argument of the function doSomething(), belongs to the namespace MyNamespace. So, it looks at that namespace to locate the declaration of doSomething().
What is the advantage of ADL?
As the simple code example above demonstrates, ADL provides convenience and ease of usage to the programmer. Without ADL there would be an overhead on the programmer, to repeatedly specify the fully qualified names, or instead, use numerous using-declarations.
Why the criticism of ADL?
Over-reliance on ADL can lead to semantic problems, and catch the programmer off guard sometimes.
Consider the example of std::swap, which is a standard library algorithm to swap two values. With the ADL one would have to be cautious while using this algorithm because:
std::swap(obj1,obj2);
may not show the same behavior as:
using std::swap; swap(obj1, obj2);
With ADL, which version of swap function gets called would depend on the namespace of the arguments passed to it.
If there exists a namespace A, and if A::obj1, A::obj2, and A::swap() exist, then the second example will result in a call to A::swap(), which might not be what the user wanted.
Further, if for some reason both A::swap(A::MyClass&, A::MyClass&) and std::swap(A::MyClass&, A::MyClass&) are defined, then the first example will call std::swap(A::MyClass&, A::MyClass&) but the second will not compile because swap(obj1, obj2) would be ambiguous.
Trivia:
Why is it sometimes called a “Koenig lookup”?
Because it was devised by former AT&T and Bell Labs researcher and programmer, Andrew Koenig, although Koenig himself disputes this in a 2012 blog post:
The reason that my name is associated with argument-dependent lookup is that although I did not invent the idea, I did recognize that the introduction of namespaces causes a severe problem that ADL, or something similar, was needed to solve.
Further reading:
- Herb Sutter’s Name Lookup on GotW
- Standard C++03/11 [basic.lookup.argdep]: 3.4.2 Argument-dependent name lookup.
**1** The definition of ADL is as defined in Josuttis’ book, *The C++ Standard Library: A Tutorial and Reference*.