C++ ADL (Argument-Dependent Lookup): Namespaces & Operators
이 글의 핵심
ADL lets unqualified calls find functions in namespaces associated with argument types—essential for operators and the swap two-step.
What is ADL?
Also called Koenig lookup: when resolving a unqualified function call, the compiler also searches namespaces associated with the argument types.
namespace MyLib {
struct Point {
int x, y;
};
void print(const Point& p) {
std::cout << "(" << p.x << ", " << p.y << ")" << std::endl;
}
}
int main() {
MyLib::Point p{10, 20};
// ADL finds MyLib::print
print(p); // no MyLib:: prefix needed
MyLib::print(p); // qualified also works
}
How it works
namespace A {
struct X {};
void func(X) {
std::cout << "A::func" << std::endl;
}
}
namespace B {
void func(A::X) {
std::cout << "B::func" << std::endl;
}
}
int main() {
A::X x;
func(x); // ADL: A::func
B::func(x); // qualified: B::func
}
std::cout and ADL
#include <iostream>
namespace MyLib {
struct Point {
int x, y;
};
std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << "(" << p.x << ", " << p.y << ")";
}
}
int main() {
MyLib::Point p{10, 20};
// ADL finds MyLib::operator<<
std::cout << p << std::endl;
}
Practical examples
Example 1: Operators
namespace Math {
struct Vector2 {
float x, y;
};
Vector2 operator+(const Vector2& a, const Vector2& b) {
return {a.x + b.x, a.y + b.y};
}
Vector2 operator-(const Vector2& a, const Vector2& b) {
return {a.x - b.x, a.y - b.y};
}
Vector2 operator*(const Vector2& v, float scalar) {
return {v.x * scalar, v.y * scalar};
}
std::ostream& operator<<(std::ostream& os, const Vector2& v) {
return os << "Vector2(" << v.x << ", " << v.y << ")";
}
}
int main() {
Math::Vector2 v1{1.0f, 2.0f};
Math::Vector2 v2{3.0f, 4.0f};
auto v3 = v1 + v2;
auto v4 = v1 - v2;
auto v5 = v1 * 2.0f;
std::cout << v3 << std::endl;
std::cout << v4 << std::endl;
std::cout << v5 << std::endl;
}
Example 2: swap
namespace MyLib {
struct BigObject {
std::vector<int> data;
BigObject(size_t size) : data(size) {}
};
void swap(BigObject& a, BigObject& b) noexcept {
a.data.swap(b.data);
std::cout << "MyLib::swap" << std::endl;
}
}
int main() {
MyLib::BigObject obj1(1000);
MyLib::BigObject obj2(2000);
using std::swap; // fallback
swap(obj1, obj2); // ADL may pick MyLib::swap
}
Example 3: Comparisons
namespace Data {
struct Record {
int id;
std::string name;
};
bool operator==(const Record& a, const Record& b) {
return a.id == b.id;
}
bool operator!=(const Record& a, const Record& b) {
return !(a == b);
}
bool operator<(const Record& a, const Record& b) {
return a.id < b.id;
}
}
int main() {
Data::Record r1{1, "Alice"};
Data::Record r2{2, "Bob"};
if (r1 == r2) {
std::cout << "equal" << std::endl;
}
if (r1 < r2) {
std::cout << "r1 less" << std::endl;
}
std::vector<Data::Record> records = {r2, r1};
std::sort(records.begin(), records.end());
}
Example 4: Serialization
namespace Serialization {
struct Serializer {
std::ostringstream stream;
std::string str() const {
return stream.str();
}
};
struct Point {
int x, y;
};
Serializer& operator<<(Serializer& s, const Point& p) {
s.stream << "{x:" << p.x << ",y:" << p.y << "}";
return s;
}
Serializer& operator<<(Serializer& s, const std::vector<Point>& points) {
s.stream << "[";
for (size_t i = 0; i < points.size(); i++) {
if (i > 0) s.stream << ",";
s << points[i]; // ADL
}
s.stream << "]";
return s;
}
}
int main() {
Serialization::Serializer s;
Serialization::Point p1{10, 20};
Serialization::Point p2{30, 40};
s << p1;
std::cout << s.str() << std::endl;
std::vector<Serialization::Point> points = {p1, p2};
Serialization::Serializer s2;
s2 << points;
std::cout << s2.str() << std::endl;
}
Lookup scope notes
namespace A {
struct X {};
}
namespace B {
void func(A::X) {
std::cout << "B::func" << std::endl;
}
}
namespace C {
void test() {
A::X x;
// func(x); // error: not found in C
// ADL searches namespaces associated with argument types (here A), not C
}
}
Nested namespaces
namespace Outer {
namespace Inner {
struct X {};
void func(X) {
std::cout << "Inner::func" << std::endl;
}
}
}
int main() {
Outer::Inner::X x;
func(x); // ADL: Outer::Inner::func
}
Common pitfalls
Pitfall 1: Unexpected overload
namespace MyLib {
struct Data {};
void process(Data) {
std::cout << "MyLib::process" << std::endl;
}
}
void process(MyLib::Data) {
std::cout << "global::process" << std::endl;
}
int main() {
MyLib::Data d;
process(d); // ADL: MyLib::process
::process(d); // global::process
}
Pitfall 2: Templates
namespace MyLib {
struct X {};
void func(X) {
std::cout << "MyLib::func" << std::endl;
}
}
template<typename T>
void call(T value) {
func(value); // ADL applies here
}
int main() {
MyLib::X x;
call(x); // MyLib::func
}
Pitfall 3: using declarations
namespace A {
struct X {};
void func(X) { std::cout << "A::func" << std::endl; }
}
namespace B {
void func(A::X) { std::cout << "B::func" << std::endl; }
}
int main() {
using B::func;
A::X x;
func(x); // ambiguous: A::func vs B::func
}
Pitfall 4: Built-in types
void func(int) {
std::cout << "func(int)" << std::endl;
}
namespace MyLib {
void func(int) {
std::cout << "MyLib::func(int)" << std::endl;
}
}
int main() {
int x = 10;
func(x); // global::func (no ADL for int)
MyLib::func(x); // qualified
}
Disabling ADL (mostly)
namespace MyLib {
struct X {};
void func(X) { std::cout << "MyLib::func" << std::endl; }
}
void func(MyLib::X) {
std::cout << "global::func" << std::endl;
}
int main() {
MyLib::X x;
func(x); // ADL: MyLib::func
(func)(x); // no ADL: global::func
::func(x); // global::func
}
Best practices
namespace MyLib {
struct Point {
int x, y;
};
Point operator+(const Point& a, const Point& b) {
return {a.x + b.x, a.y + b.y};
}
void swap(Point& a, Point& b) noexcept {
std::swap(a.x, b.x);
std::swap(a.y, b.y);
}
std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << "(" << p.x << ", " << p.y << ")";
}
}
// Avoid dumping operators in global namespace for library types
FAQ
Q1: When does ADL apply?
A:
- Unqualified calls
- Associated namespaces of argument types
- Especially operators
Q2: Why does it exist?
A:
- Less verbose than always qualifying
- Natural operator syntax
- Cleaner generic code
Q3: How to disable?
A:
(func)(x)::func(x)
Q4: Built-in types?
A: No associated namespace—no ADL.
Q5: Pitfalls?
A:
- Surprise overload resolution
- Name clashes—qualify when needed
Q6: Learning resources?
A:
- Effective C++
- C++ Templates
- cppreference.com
Related posts (internal links)
- Namespace guide
- Operator overloading
- Operator precedence
Practical tips
Debugging
- Warnings first
Performance
- Profile
Code review
- Conventions
Practical checklist
Before coding
- Right approach?
- Maintainable?
- Performance?
While coding
- Warnings?
- Edge cases?
- Errors?
At review
- Intent?
- Tests?
- Docs?
Keywords
C++, ADL, argument-dependent lookup, namespace, operators
Related posts
- Function objects
- Namespace complete
- Operator overloading
- Operator precedence
- User-defined literals