Situation: Multiple Namespaces
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
Functions are looked up in the namespace(s) of the argument type(s) involved in a call expression:
pixl::point px {640, 480};
geom::point gm {1.1, 2.3};
draw(px); // ADL ⇒ call pixl::draw
draw(gm); // ADL ⇒ call geom::draw
Argument px
has type pixl::point
⇒
ADL looks for function
in namespace draw
pixl
⇒ matching function found and called.
Explicit Qualification ⇒ No ADL Without 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
Standard Library Example Standard Lib Example Example
The following call to std::sort
works without namespace qualification:
#include <vector>
#include <algorithm>
#include <iostream>
int main () {
std::vector<int> v {3,2,1,4};
sort(begin(v), end(v)); // compiles!
for (auto x : v) std::cout << x << ' ';
std::cout << '\n';
}
type of v
is std::vector
- ADL looks for
begin
/end
in namespacestd
std::begin
/std::end
found and called- return type of
begin
/end
isstd::vector<int>::iterator
- ADL looks for
sort
in namespacestd
std::sort
found and called
Why?
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!
Example: Class point
in 2 Namespaces
- want support for expressions like
5 * point{4,1}
- need functions
operator * (NUMBER, point)
- left hand operand not a
point
⇒operator*
cannot be member function ofpoint
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&);
}
If there was no ADL
- would need explicit qualification with
::
auto p = pixl::operator*(2, px);
- clumsy call syntax defeats purpose of operators (readability, infix notation, terseness)
With ADL
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;
ADL is used
to look up name f
, if
…
f
is part of a function call expression, like e.g.,f(arg)
f
is the name of a functionf
is called with at least one argument
ADL is not used
to look up name f
, if
…
f
- 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 namedf
in the lookup candidate set - there is already a block-scope declaration of function
f
in the lookup candidate set (types declared with
are ignored)using
- there is already a declaration of any other
(variable/object, …)
f
that is not a function or function template in the lookup candidate set
ADL is exclusively used for looking up …
non-member functions
begin
andend
taking
inrange
for(auto const& x : range) { … }
if member-
begin
or member-end
could not be found inrange
's class- non-member function
needed by structured bindingsget
- names that depend on a template parameter (when a template is instantiated)
namespace ns {
class C { … };
void f (C);
}
…
ns::C x;
expression | compiles? | explanation |
---|---|---|
f(x); |
f is a function name and x has class type
⇒ ADL finds f | |
ns::f(x); |
call explicitly qualified with ns:: ⇒ no ADL | |
(f)(x); |
(f) is not a simple function name, but an expression
⇒ no ADL ⇒ function not found | |
using ns::f; |
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;
expression | compiles? | explanation |
---|---|---|
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 | |
using ns::h; |
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;
expression | compiles? | explanation |
---|---|---|
lm(x); |
lm is not a function,
but a compiler-generated function object
⇒ no ADL ⇒ lm not found | |
using ns::lm; |
no ADL but object lm
was explicitly made visible for regular name lookup |
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 Examines | Found |
---|---|---|
f(0); |
NO ADL (int doesn't have namespace) | NOTHING |
na::f(0); |
explicit qualification ⇒ NO ADL | na::f |
nb::B b; |
(nb::B ≡ na::A) ⇒ A, na |
NOTHING |
nb::B b; |
(nb::B ≡ na::A) ⇒ 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 Examines | Found |
---|---|---|
lib::A a; |
lib → lib::v2 |
lib::v2::f( |
lib::A a; |
explicit qualification ⇒ NO ADL | lib::v2::f( |
lib::A a; |
explicit qualification ⇒ NO ADL | lib::v1::f( |
lib::B b; |
lib::v2 → lib |
lib::g( |
lib::v1::B b1; |
lib::v1 → lib |
NOTHING
no such function g( |
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:
-
hidden friends
like
nx::f(nx::X::C)
namespace na {
class A { … };
void f (A);
}
namespace nb {
class B : public na::A { … };
}
namespace nx {
class X { public: …
class C : public nb::B { … };
friend void f(C);
};
void f (X);
}
Call | ADL Examines | Found |
---|---|---|
na::A a; |
A, na |
na::f(na::A) |
nb::B b; |
B, A, nb, na |
// B → A |
nx::X x; |
X, nx |
nx::f(nx::X) |
nx::X::C c; |
C, X, B, A, |
// friend of X |
nx::X::C c; |
explicit qualification ⇒ NO ADL | NOTHING
nx::f(nx::X::C)
only visible to ADL! |
ADL looks in:
- enclosing namespace of
E
- enclosing class of
E
Only ADL can find:
-
hidden friends
like
ns::f(ns::C::E)
namespace ns {
class C { public: …
enum class E { x, y, z };
friend void f(E);
};
void f (int, C::E);
}
Call | ADL Examines | Found |
---|---|---|
f(0,C::E::x); |
C, ns |
ns::f(int, ns::C::E); |
f(C::E::x); |
C, ns |
// friend of C |
ADL examines associated namespaces and classes of T
namespace ns {
class T { … };
void g (T*);
}
Call | ADL Examines | Found |
---|---|---|
ns::T x; |
T, ns |
ns::g(ns::T*) |
ADL examines all 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 Examines | Found |
---|---|---|
na::A nc::C::* p = &nc::C::x; |
A, na, C, nc |
na::f |
na::A nc::C::* p = &nc::C::x; |
A, na, C, nc |
nc::g |
namespace na {
class A { };
void f ( void(*)(A) ) {}
}
void g(na::A);
Call | ADL Examines | Found |
---|---|---|
void(*p)(na::A) = g; |
A, na |
na::f |
namespace na {
class A { … };
}
namespace nc {
class C { public: …
void set(na::A);
};
void f ( void(nc::C::*)(na::A) );
}
Call | ADL Examines | Found |
---|---|---|
f( &nc::C::set ); |
C, nc, A, na |
nc::f |
f
functions)namespace na {
class A { … };
// set of overloads
void f (A);
void f (A, int);
void g ( void(*)(A) );
}
Call | ADL Examines | Found |
---|---|---|
g( &na::f ); |
A, na |
na::g |
ADL looks in:
- template type itself (
X
) and its associated namespaces (nx
) - types of all type template arguments (
A1
) - template template arguments (e.g.,
P2
=A2
): classes in which they are members - template template arguments (e.g.,
P2
=A2
): enclosing namespaces (n2
)
ADL skips arguments for:
- non-type template parameters (
int P3
) - template template parameters (e.g.,
P2
=A2
: ADL skipsA2
itself)
namespace nx {
template<class P1, template<class> class P2, int P3>
class X {…};
}
namespace n1 { class A1; }
// specializations of X used as parameter:
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& );
}
// instantiation:
nx::X< n1::A1, n2::A2, 5> spx {};
Call | ADL Examines | Found |
---|---|---|
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 Idioms
template<class T>
void generic_function (…) {
T x;
T y;
…
using std::swap;
swap(x, y);
…
}
This makes swap
a customization point:
- if custom
swap(T&,T&)
declared inT
's namespace
⇒visible
to ADL ⇒ custom swap will be used - if no special overload for
T
exists
⇒std::swap
will be used as fallback (made visible withusing
directive)
If you want to suppress ADL and make sure that std::swap
is
always used, namespace-qualify the function call:
std::swap(x,y)
.
Use the fallback idiom only in generic (template) code and only
if a common fallback implementation, like e.g. std::swap
exists.
C++20 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.
Hidden Friends
namespace math {
class ratio { …
public:
explicit
ratio (int64_t numerator, int64_t denominator=1);
…
friend ratio operator + (ratio a, ratio b) { … }
};
}
Only ADL can find math::operator+(ratio,ratio)
- declaring it friend of
ratio
makes it a member of namespacemath
- it was not directly declared in namespace scope
- it was not directly defined (=implemented) in namespace scope
// ADL finds math::operator+(ratio,ratio)
math::ratio x {2,3}, y {1,6};
math::ratio z = x + y;
// qualified call ⇒ No ADL ⇒ operator+
not found!
math::ratio x {2,3}, y {1,6};
math::ratio w = math::operator+(x,y); // COMPILER ERROR
class B { };
class A { public:
A() {}
A(B) {} // implicit
A(int) {} // implicit
friend void foo (A) { }
friend void foo (B) { }
friend void foo (int) { }
};
void bar (A) { }
A a1{ }; //
A a2{ B{} }; //
A a3{ 47 }; //
bar( A{} ); //
// implicit conversions:
bar( B{} ); // bar( A{B{}} )
bar( 123 ); // bar( A{123} )
// hidden friend 'foo':
foo( A{} ); //
// can't find foo(B) & foo(int),
// no implicit conversions to 'A'
foo( B{} ); //
foo( 123 ); //
namespace math {
class tiny_ratio {
// no operator+! (on purpose)
…
};
class ratio {
…
public:
explicit
ratio(int64_t numerator, int64_t denominator=1);
// implicit conversion from tiny_ratio:
ratio(tiny_ratio r);
…
friend ratio operator + (ratio a, ratio b) { … }
};
}
// No implicit conversions possible:
math::tiny_ratio r{1,2}, s{1,4};
auto t = r + s; // COMPILER ERROR ⇒ good!
The hidden friend version of operator+(ratio,ratio)
can only be
found by ADL, so only if a ratio
argument is involved in the call.
Since we can implicitly construct a ratio
from a tiny_ratio
any non-hidden function taking ratio
as input
(by value or non-const reference) could also be called with
tiny_ratio
objects.
So, if operator+(ratio,ratio)
had been declared in namespace
scope instead of as hidden friend, we could use it to add
tiny_ratio
objects despite the fact that we explicitly
did not want that!
Hidden friends also protect against conversion operators that
implicitly convert other types to ratio
!
namespace math {
class ratio {
…
public:
friend ratio operator + (ratio a, ratio b) { … }
…
};
class my_num {
…
public:
…
// conversion operator that can
// create a 'ratio' from a 'my_num'
operator ratio() const { return ratio{…}; }
// forgot operator + for my_num!
};
}
math::my_num u = 23;
math::my_num v = 47;
auto w = u + v ; // COMPILER ERROR: operator+ not found
The hidden friend version of operator+(ratio,ratio)
can only be
found by ADL, so only if a ratio
argument is involved in the call.
Since my_num::operator ratio()
can convert any my_num
into a ratio
, any non-hidden function taking ratio
as input (by value or non-const reference) can also be called with
my_num
objects.
So, if operator+(ratio,ratio)
had been declared in namespace
scope instead of as hidden friend, the last line would have compiled
and yielded a math::ratio w
!
Goal: prevent ADL from picking up custom overloads of a generic library function
Idea: Use Function Object
#include <iostream>
namespace lib {
namespace detail {
struct foo_fn {
template<class T>
void operator() (T const& x) const { std::cout << "lib::detail::foo_fn::operator()\n";… }
};
}
// global function object lib::foo
inline constexpr detail::foo_fn foo {};
// note: inline variable requires C++17
}
namespace na {
class A { };
}
namespace nb {
class B { };
// custom foo
void foo(B const& b) { std::cout << "nb::foo\n"; }
}
int main () {
na::A a;
nb::B b;
// with explicit qualification:
lib::foo(a);
lib::foo(b);
// same with using statement:
using lib::foo;
foo(a);
foo(b);
}
⇒ Consistent Behavior!
#include <iostream>
namespace lib {
namespace detail {
struct foo_fn {
template<class T>
void operator() (T const& x) const {
std::cout << "lib::detail::foo_fn::operator()\n";
}
};
}
// global function object lib::foo
inline constexpr detail::foo_fn foo {};
// note: inline variable requires C++17
}
namespace na {
class A { … };
}
namespace nb {
class B { … };
// custom foo
void foo(B const& b) {std::cout << "nb::foo\n";…}
}
int main () {
na::A a;
nb::B b;
// with explicit qualification:
lib::foo(a); // lib::foo → lib::detail::foo
lib::foo(b); // lib::foo → lib::detail::foo
// same with using statement:
using lib::foo;
foo(a); // lib::foo → lib::detail::foo
foo(b); // lib::foo → lib::detail::foo
}
- qualified and unqualified calls have the same effect
- users cannot override library function's behavior
C++20's standard library requires some functions to not take part in ADL.
These are also known as Niebloids
(named after Eric Niebler).
These Niebloids can be implemented as function objects as presented here
but could also make use of special compiler intrinsics to disable ADL.
Customization Point Objects Customization Objects CPOs C++20
Goal: consistent concept checking on (potential) customization points of a generic library!
The traditional 2-Step 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
.
Idea: Function Object as Single Entry Point Idea: Single-Entry Function Object
All objects passed to any 'foo
' implementation
should satisfy concept 'Fooable
':
#include <iostream>
namespace lib {
template<class T> concept Fooable = requires (T x) {
{x.size()} -> std::integral; };
namespace detail {
// prevent ADL from picking up 'foo' in 'lib'
void foo () = delete;
inline namespace defaults {
template<Fooable T> // constrained!
void foo (T const&) { std::cout << "lib::detail::defaults::foo\n";… }
}
struct foo_fn {
template<Fooable T> // constrained!
void operator() (T const& x) const {
// calls custom foo found by ADL
// or fallback defaults::foo
foo(x);
}
};
}
// expose default impl. (if we want that)
namespace defaults = detail::defaults;
// prevent conflicts with hidden friends 'foo' in 'lib'
inline namespace foo_cpo {
// global function object lib::foo
inline constexpr detail::foo_fn foo {};
}
}
namespace na {
class A { public: size_t size() const; };
}
namespace nb {
class B { public: int size() const; };
void foo(B const&) { std::cout << "nb::foo\n";}
}
int main () {
na::A a;
nb::B b;
using lib::foo;
foo(a);
foo(b);
// explicit qualification has no effect:
lib::foo(a);
lib::foo(b);
// force default implementation:
lib::defaults::foo(a);
lib::defaults::foo(b);
}
Make sure that your default implementation (here lib::defaults::foo
)
is not unconstrained or underconstrained!
Otherwise it might act as catch-all and custom functions (found by ADL)
won't be selected!
Functions with forwarding reference parameters and/or
variadic parameter packs are especially prone to being catch-alls.
⇒ Consistent Behavior
#include <iostream>
namespace lib {
template<class T> concept Fooable = requires (T x) {
{x.size()} -> std::integral; };
namespace detail {
// prevent ADL from picking up 'foo' in 'lib'
void foo () = delete;
inline namespace defaults {
template<Fooable T> // constrained!
void foo(T const&) { std::cout << "lib::detail::defaults::foo\n"; }
}
struct foo_fn {
template<Fooable T> // constrained!
void operator() (T const& x) const {
// calls custom foo found by ADL
// or fallback defaults::foo
foo(x);
}
};
}
namespace defaults = detail::defaults;
inline namespace foo_fpo {
// global function object lib::foo
inline constexpr detail::foo_fn foo {};
}
}
namespace na {
class A { public: size_t size() const; };
}
namespace nb {
class B { public: int size() const; };
void foo (B const&){ std::cout << "nb::foo\n";};
}
int main () {
na::A a;
nb::B b;
using lib::foo;
foo(a); // lib::foo → fallback lib::defaults::foo
foo(b); // lib::foo → custom nb::foo
// explicit qualification has no effect:
lib::foo(a); // lib::foo → fallback lib::defaults::foo
lib::foo(b); // lib::foo → custom nb::foo
// force default implementation:
lib::defaults::foo(a);
lib::defaults::foo(b);
}
- all calls are constrained with
Fooable
because they all invokelib::detail::foo_fn::operator()
lib::foo(…)
always does the same thing; no need for ADL+Fallback (ADL 2-step
) because ADL is used automatically bylib::foo
- might be surprising that forcing the default implementation
is not possible with explicit qualification
lib::foo
(becauselib::foo
is an object)
⇒ Cannot Bypass Constraints!
namespace lib {
template<class T> concept Fooable = requires (T x) {
{x.size()} -> std::integral; };
…
}
Calls to lib::foo
will never pick up unconstrained
custom foo
overloads:
namespace nc {
class C { }; // no member 'size()'
void foo (C const&);
}
nc::C c;
lib::foo(c); COMPILER ERROR: C doesn't satisfy 'lib::Fooable'
using lib::foo;
foo(c); COMPILER ERROR: C doesn't satisfy 'lib::Fooable'
class D { public:
// non-integral return type:
double size() const { … }
friend void foo (D const&) { … }
};
D d;
lib::foo(d); COMPILER ERROR: D doesn't satisfy 'lib::Fooable'
using lib::foo;
foo(d); COMPILER ERROR: D doesn't satisfy 'lib::Fooable'
Comments…