Radical ADL (Argument-dependent lookup)

激进的ADL(Argument-dependent lookup)

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
2
3
4
5
6
7
8
9
10
11
namespace Chrono{
class Date{/*...*/};
bool operator==(const Date&,const std::string&);
std::string format(const Date&)
// ....
}

void f(Chrono::Date d,int i){
std::string s=format(d); //Chrono::format
std::string t=format(i); // error,no matching function
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace NS {
class T { };
void f(T){
cout<<"NS::F(T)"<<endl;
}
void g(T, int){
cout<<"NS::g(T,int)"<<endl;
}
}
NS::T parm;
void g(NS::T, float){
cout<<"::g(NS::T,float)"<<endl;
}
int main() {
f(parm); // OK: calls NS::f
g(parm, 1); // OK: calls NS::g(T,int)
extern void g(NS::T, float);
g(parm, 1); // OK: calls g(NS::T, float)
}

// run result
NS::F(T)
NS::g(T,int)
::g(NS::T,float)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace A{
class base{
int x;
float y;
};
void copy(base *x,base* y){
std::cout<<"A::copy"<<std::endl;
}
}
namespace B{
void copy(A::base* x,A::base* y){
std::cout<<"A::copy"<<std::endl;
}
void algo(A::base& x,A::base& y){
// Which copy function to call?
copy(&x,&y);
}
}

int main(){
A::base x,y;
B::algo(x,y);
}

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
2
3
4
5
6
7
8
9
10
void algo(A::base& x,A::base& y){
// using A::copy or B::copy;
using A::copy;
copy(&x,&y,false);
B::copy(&x,&y,false);
}

// run result
A::copy()
B::copy()

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void g(int){
cout<<"::g(int)"<<endl;
}
struct A{
void g(char){
cout<<"A::g(char)"<<endl;
}
void h(char){
cout<<"A::h(char)"<<endl;
}
};

template<typename T>
class B:public T{
public:
void f(){
// Which g() to call?
g(2);
}
};

void f(B<A> x){
x.f();
}

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 and definition 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 call namespaceName::funcName())
  • Place the function name in parentheses ((funcName)())
1
2
3
4
5
6
7
8
9
10
11
12
13
namespace TEST{
struct A{};
void func(const A& x){
cout<<"fuck"<<endl;
}
}

int main()
{
TEST::A x;
func(x); // call TEST::func
(func)(x); // error: use of undeclared identifier 'func';
}
The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:Radical ADL (Argument-dependent lookup)
Author:LIPENGZHA
Publish Date:2016/11/29 22:39
Word Count:3.4k Words
Link:https://en.imzlp.com/posts/25788/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!