Argument Dependend Lookup ADL

What It Does What?

namespace pixl {
  class point { 
      point(int x, int y); 
  };
  void draw (point const&);
  void print (int);
}
namespace geom {
  class point { 
      point(double x, double y); 
  };
  void draw (point const&);

}

With ADL

Function is selected based on the namespace of the argument type(s) it is called with.

pixl::point px {640, 480};
geom::point gm {1.1, 2.3};

draw(px);  // ADL selects pixl::draw
draw(gm);  // ADL selects geom::draw

Explicit Qualification ⇒ no ADL Without ADL

No ADL is performed if a function call is explicitly qualified with a namespace name.

Note that explicit qualification is necessary, if none of the function arguments has an associated namespace (like, e.g., fundamental types: int, double, …).

pixl::draw(px);
geom::draw(gm);

pixl::draw(gm);  //  COMPILER ERROR: expects pixl::point
geom::draw(px);  //  COMPILER ERROR: expects geom::point

// needs qualification
pixl::print(5);
// because 'int' is not in namespace 'pixl'
print(5);        //  COMPILER ERROR: 'print' not found

Why?

Consistent Member/Non-Member Calls Consistent Function Calls Consistency

object.fn(); looks up member function in object's class
fn(object); looks up function in namespace of object's class

Non-member functions mentioning class X (e.g., taking an argument of type X) that are in the same namespace as X are also part of X's interface.

Non-Member Operators! Non-Member Operators! Operators!

  • want support for expressions like 5 * point{4,1}
  • need functions operator * (NUMBER, point)
  • left hand operand not a pointoperator * cannot be member function of point
namespace pixl {
  class point { 
      point(int x, int y); 
  };
  // cannot be member function:
  point operator * (int factor, point const&);
}
namespace geom {
  class point { 
      point(double x, double y); 
  };
  point operator * (double factor, point const&);
}
  • would need explicit qualification with ::
    auto p = pixl ::operator*(2, px);
  • clumsy call syntax defeats purpose of operators (terseness, readability)

Operator functions are selected based on the namespace of their operand type(s):

pixl::point px {640, 480};
geom::point gm {1.1, 2.3};

// px has type pixl::point// ⇒ ADL selects pixl::operator*
auto p = 2 * px;  

// gm has type geom::point// ⇒ ADL selects geom::operator*
auto x = 2 * gm;

When and Where? Where?

General Rules

  • f is part of a function call expression, like e.g., f(arg)
  • f is the name of a function
  • f is called with at least one argument
  • the call to f is qualified, e.g., ns::f(arg)
  • the name f itself is contained in an expression like, e.g., (f)(arg)
  • there is already a class member named f in the lookup candidate set
  • there is already a block-scope declaration of function f in the lookup candidate set (types declared with using are ignored)
  • there is already a declaration of any other (variable/object, …) f that is not a function or function template in the lookup candidate set
  • non-member functions begin and end taking range in

    for(auto const& x : range) { }

    if member-begin or member-end could not be found in range's class

  • non-member function get needed by structured bindings
  • names that depend on a template parameter (when a template is instantiated)

When Calling Functions Function Calls

namespace ns {
  class C {  };
  void f (C);
}

ns::C x;
f(x); f is a function name and x has class type ⇒ ADL finds f
ns::f(x); explicit qualification with ns::no ADL
(f)(x); (f) is not a simple function name, but an expression ⇒ no ADL ⇒ function not found
using ns::f;
(f)(x);
no ADL, but f was explicitly made visible for regular name lookup

When Calling Function Templates Calling Function Templates Template Calls

namespace ns {
  class C {  };
  template<class T> g(T);
  template<int i> h(C);
}

ns::C x;
g(x); ADL finds ns::g
ns::g(x); qualification with ns::no ADL
g<ns::C>(x); g<ns::C> is not a simple function name ⇒ no ADL ⇒ function template not found
ns::h<0>(x); qualification with ns::no ADL
h<0>(x); h<0> is not a simple function name ⇒ no ADL ⇒ function template not found
function template ns::h was explicitly made visible for regular name lookup

When Calling Lambdas ⇒ No ADL Calling Lambdas ⇒ No ADL Not For Lambdas

namespace ns {
  class C {  };
  auto lm = [](C a) {  };  // lambda
}

C x;
lm(x); lm is not a function, but a compiler-generated function objectno ADLlm not found
using ns::lm;
lm(x);
but object lm was explicitly made visible for regular name lookup

How Does It Work? How?

ADL examines the type of each argument in a function call expression f(arg1, arg2, )
(in no particular order) and determines the set of associated namespaces and classes which will also be searched for a function (or function template) named f.

No Associated Namespace For No Assoc. Namespace For ADL ignores

  • fundamental types (int, double, …)
  • aliases introduced by using directives (B)
namespace na {
  class A {  };
  void f(int);
  void g(A);
}
namespace nb {
  using B = na::A;
  void h(B);

}
Call ADL Examins Finds
f(0); (int doesn't have namespace) NOTHING
na::f(0); explicit qualification ⇒ NO ADL na::f
nb::B b;
h(b);
A (nb::B ≡ na::A), na NOTHING
nb::B b;
g(b);
A, na na::g

Inline Namespaces

ADL looks in:
  • enclosing namespace of inline namespace
  • inline namespaces of all previously found namespaces
namespace lib {
  class A {  };

  inline namespace v2 {
    class B {  };
    void f(A const&);
  }
  namespace v1 {
    class B {  };
    void f(A const&);
  }

  void g(B const&);  // B refers to v2::B
}
Call ADL Examins Finds
lib::A a;
f(a);
liblib::v2 lib::v2::f(lib::A);
lib::A a;
lib::v2::f(a);
explicit qualification ⇒ NO ADL lib::v2::f(lib::A);
lib::A a;
lib::v1::f(a);
explicit qualification ⇒ NO ADL lib::v1::f(lib::A);
lib::B b;
f(b);
lib::v2lib lib::g(lib::v2::B);
lib::v1::B b1;
f(b1);
lib::v1lib NOTHING
no such function
g(lib::v1::B)

Classes class C class Args

ADL looks in:
  • C itself
  • base classes: direct (B) + indirect (A)
  • any class of which C is a member (X)
  • innermost enclosing namespaces of all classes in the set (na, nb, nx)
Only ADL can find:
namespace na {
  class A {  };
  void f(A);
}
namespace nb {
  class B : public na::A {  };
}
namespace nx {
  class X { 
      class C : public nb::B {  };
      friend void f(C);
  };
  void f(X);
}
Call ADL Examins Finds
na::A a;
f(a);
A, na na::f(na::A)
nb::B b;
f(b);
B, A, nb, na // B → A
na::f(na::A)
nx::X x;
f(x);
X, nx nx::f(nx::X)
nx::X::C c;
f(c);
C, X, B, A,
nx, nb, na
// friend of X
nx::f(nx::X::C)
nx::X::C c;
nx::f(c);
explicit qualification ⇒ NO ADL NOTHING
nx::f(nx::X::C)
only visible to ADL!

Enumerations enum (class) E Enumeration Args

ADL looks in:
  • enclosing namespace of E
  • enclosing class of E
Only ADL can find:
namespace ns {
  class C { 
    enum class E { x, y, z };
    friend void f(E);
  };
  void f(int, E);
}
Call ADL Examins Finds
f(0,C::E::x); C, ns ns::f(int, ns::C::E);
f(C::E::x); C, ns // friend of C ns::f(ns::C::E);

Pointers/Addresses Pointer Args

Raw Pointer (T*) T*

ADL examins associated namespaces and classes of T
namespace ns {
  class T {  };
  void g(T*);
}
Call ADL Examins Finds
ns::T x;
g(&x);
T, ns ns::g(ns::T*)

Pointer To Class Data Member C::*

ADL examins associated namespaces and classes of the member type (A) and the containing class (C)
// forward declaration of C
namespace nc { class C; } 
namespace na {
  class A {  };
  // pointer ↓ to C member
  void f( A nc::C::* );
}
namespace nc {
  class C { public: 
    na::A x, y, z;
  };
  // pointer ↓ to C member
  void g( na::A C::* );
}
Call ADL Examins Finds
na::A nc::C::* p = &nc::C::x;
f(p);
A, na, C, nc na::f
na::A nc::C::* p = &nc::C::x;
g(p);
A, na, C, nc nc::g

Function Pointer Y(*)(X)

ADL examins associated namespaces and classes of all parameter types and the return type
namespace na {  
  class A { }; 
  void f( void(*)(A) ) {}
}

void g(na::A);
Call ADL Examins Finds
void(*p)(na::A) = g;
f(p);
A, na na::f

Pointer To Class Member Function Y(C::*)(X)

ADL examins associated namespaces and classes of all parameter types, the return type and the containing class type
namespace na {
  class A {  };
}
namespace nc {
  class C { public: 
    void set(na::A);
  };
  void f( void(nc::C::*)(na::A) );
}
Call ADL Examins Finds
f( &nc::C::set ); C, nc, A, na nc::f

Address Of Overloaded Function &(Y(*)(X))

ADL examins every function or function template in the set of overloads (here both f functions)
namespace na {
  class A {  };
  // set of overloads
  void f(A);
  void f(A, int);

  void g( void(*)(A) );
}
Call ADL Examins Finds
g( &na::f ); A, na na::g

Class Template Specializations Class Template Spec. Args

ADL looks in:
  • template type itself (X) and its associated namespaces (nx)
  • types of all type template arguments (A1)
  • template template arguments (e.g., A2 for P2): classes in which they are members
  • template template arguments (e.g., A2 for P2): enclosing namespaces (n2)
ADL skips arguments for:
  • non-type template parameters (int)
  • template template parameters (P2: skips arg A2 itself)
namespace nx {
  template<class P1, template<class> class P2, int> class X;template<class P1,
        template<class> class P2, int
> class X;
}
namespace n1 { class A1; }
namespace n2 {
  template<class> class A2 { public:
    friend void h( X<n1::A1,A2,5> const& );
  };
  void g( X<n1::A1,A2,5> const& );
}
namespace nx {
  void f( X<n1::A1,n2::A2,5> const& );
}

nx::X< n1::A1, n2::A2, 5> spx {};
Call ADL Examins Finds
f(spx); X, nx, A1, n1, n2 nx::f
g(spx); X, nx, A1, n1, n2 n2::g
h(spx); X, nx, A1, n1, n2 NOTHING

ADL-Related Idioms Idioms

ADL & Fallback

// non-generic 
void fn () {
    SomeType x;
    SomeType y;
    
    using std::swap;
    swap(x, y);
}
template<class T>
void generic_fn () {
    T x;
    T y;
    
    using std::swap;
    swap(x, y);
}
This makes swap a customization point:
  • if custom swap(T&,T&) declared in T's namespace
    visible to ADL ⇒ will be used
  • if no special overload for Tstd::swap will be used as fallback (made visible with using directive)

If you want to suppress ADL and make sure that std::swap is always used, just namespace-qualify the function call: std::swap(x,y).

  If you want to enforce constraints on the arguments through concepts you should use a customization point object to make sure that the constraints can't be bypassed by custom functions.

Customization Point Objects Customization Objects

Goal: consistent concept checking on (potential) customization points!

The traditional ADL & Fallback Idiom

using std::swap; // fallback
swap(x,y); // ADL may find custom swap

cannot guarantee that a custom implementation of swap constrains its parameters the same way as the fallback implementation std::swap.

namespace ns {
  template<class T> concept Fooable = ;

  namespace detail {
    // default 'foo' implementation:
    template<class T>
    int foo(T const& x) {  }

    struct foo_fn {
      template<Fooable T>
      int operator() (T const& x) const {
        // calls either ns::detail::foo         // or custom foo found by ADL
        return foo(x);
      }
    };
  }
  // global function object ns::foo
  constexpr detail::foo_fn foo {};
}
namespace na {
  class A {  };
  int foo(A const&) {}
}
namespace nb {
  class B {  };
}

na::A a; nb::B b; using ns::foo; foo(a); // ns::foo → custom ::foo foo(b); // ns::foo → fallback ns::::foo ns::foo(a); // ns::foo → custom ::foo ns::foo(b); // ns::foo → fallback ns::::foo
  • ns::foo ensures that all calls to foo (qualified or unqualified, custom or default implementation) are constrained with Fooable
  • forcing default implementation not straight-forward in client code (explicit qualification has no effect here, because ns::foo is an object)

Hidden Friends

namespace math {
  class ratio {
  public:
      ratio(int64_t num);
      ratio(int64_t numerator, int64_t denominator);
      
      friend ratio operator + (ratio const& a, ratio const& b) {
          // inline implementation ("definition")!
          return ratio{  };
      }
  };
}namespace math {
  class ratio {
  public:
    ratio(int64_t num);
    ratio(int64_t numerator, 
          int64_t denominator);
    
    friend ratio operator+(ratio const& a, 
                          ratio const& b)
    {
        // inline implementation!
        return ratio{  };
    }
  };
}

math::ratio x {2,3}; math::ratio y {1,6};
Only ADL can find math::operator+
  • declaring it friend of ratio makes it a member of namespace math
  • it was not directly declared in namespace scope
  • it was not directly defined (=implemented) in namespace scope