C++ Namespaces and Name Lookup
- Description: A note on namespaces,
usingdirectives and declarations, namespace aliases, argument-dependent lookup (ADL), the hidden friend idiom, andfriendfunctions and classes - My Notion Note ID: K2A-B1-3
- Created: 2018-09-16
- Updated: 2026-02-28
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1. Namespaces Basics
- 2. Nested and Inline Namespaces
- 3.
usingDeclarations, Directives, and Aliases - 4. Argument-Dependent Lookup (ADL)
- 5. The Hidden Friend Idiom
- 6.
friendFunctions and Classes
1. Namespaces Basics
- Namespace = named scope. Prevents identifier collisions between libraries.
namespace math {
constexpr double pi = 3.14159265358979;
double area(double r) { return pi * r * r; }
}
double a = math::area(5.0); // qualified access
- Names inside accessed via
namespace::name. - Same namespace reopenable across TUs — declarations merge.
// math.h
namespace math {
double area(double r);
}
// shapes.h
namespace math { // reopens math, adds another declaration
double perimeter(double r);
}
- Global namespace = unnamed. Refer to it explicitly with
::name:
int x = 1;
namespace ns {
int x = 2;
int y = ::x; // 1 (global x)
int z = x; // 2 (ns::x)
}
2. Nested and Inline Namespaces
// Nested namespaces (C++17 syntax — older syntax also works)
namespace company::project::util {
void helper();
}
// Older equivalent:
namespace company { namespace project { namespace util {
void helper();
}}}
// Use:
company::project::util::helper();
Inline namespaces
inlinenamespace's members visible in enclosing namespace as if declared there directly.- Common use: ABI versioning.
namespace mylib {
inline namespace v2 {
void api(); // looks like mylib::api() to users
}
namespace v1 {
void api(); // accessed only as mylib::v1::api()
}
}
mylib::api(); // calls v2::api()
mylib::v1::api(); // explicit old version
- Library authors evolve ABI without breaking source compat — switch which version is
inlineto change default while keeping older versions accessible.
3. using Declarations, Directives, and Aliases
- 3
usingconstructs:
using declaration
- Single name into current scope.
using std::cout;
using std::endl;
cout << "hi" << endl;
using directive
- All names from a namespace into current scope. Convenient but pollutes; avoid in headers.
using namespace std; // OK in a .cpp at function scope; AVOID in headers
void f() {
cout << "hi"; // works
}
- Never write
using namespace std;in a header. Every TU that includes inherits the dump → surprising collisions.
Namespace alias
- Shortens long namespace names within a scope.
namespace fs = std::filesystem;
fs::path p = "/tmp";
Type alias
using(C++11) replacestypedef; cleaner with templates:
using IntPtr = int*; // same as: typedef int* IntPtr;
using Callback = std::function<void(int)>; // function-pointer-like alias
template <typename T>
using Vec = std::vector<T>; // alias template
Vec<int> v;
4. Argument-Dependent Lookup (ADL)
- ADL (a.k.a. Koenig lookup) — rule that lets
operator<<etc. work without qualification.
Unqualified function call → compiler looks in:
- Usual scopes — current + enclosing.
- Argument types' namespaces — and friends declared inside their classes.
namespace ns {
struct Widget {};
void inspect(Widget) {}
}
ns::Widget w;
inspect(w); // OK: ADL finds ns::inspect because w is in ns
- Without ADL → would need
ns::inspect(w)explicitly. - ADL makes range-based
for, swap, stream operators work cleanly across namespaces:
template <typename T>
void shuffle(T& a, T& b) {
using std::swap; // bring std::swap into scope
swap(a, b); // ADL picks T's swap if defined; falls back to std::swap
}
- Two-step pattern (
using std::swap;then unqualifiedswap) — canonical way to invoke user-customizable swap via ADL. - ADL doesn't apply to fundamental-type args (empty associated namespace set) → bare
swap(v[0], v[1])onvector<int>won't findstd::swapwithout qualification or two-step.
Why this is sometimes surprising
- ADL searches all arg-type namespaces → can match more than intended:
namespace ns2 {
struct Marker {};
void process(int, Marker) {}
}
ns2::Marker m;
process(0, m); // ADL finds ns2::process via m's namespace
- Generally a feature (ADL = customization point), but can cause unexpected overload picking.
- stdlib uses ADL deliberately for
std::swap,std::begin,std::end,<<, etc.
5. The Hidden Friend Idiom
- "Hidden friend" =
friendfunction defined inside a class body. - Only findable by ADL — not by ordinary name lookup.
- Modern idiom for non-member operators on a class:
class Money {
int cents_;
public:
explicit Money(int c) : cents_(c) {}
// Hidden friend — defined inside the class body
friend Money operator+(Money a, Money b) {
return Money(a.cents_ + b.cents_);
}
friend bool operator==(const Money& a, const Money& b) {
return a.cents_ == b.cents_;
}
};
Money x{100}, y{200};
auto z = x + y; // calls hidden friend via ADL
Why hidden friends are good:
- No template instantiation pollution — they're not function templates.
- Better overload-resolution behavior — won't match unrelated calls.
- Clear association with the class — definition lives next to it.
- No
template <typename T>boilerplate.
- Use for binary operators (
+,==,<<,<=>) of value types.
6. friend Functions and Classes
friendgrants access to private/protected members of a class to a specific function or class outside it.
class Account {
double balance_;
friend class AuditLog; // class friend
friend void freeze(Account&); // function friend
friend bool operator==(const Account&, const Account&) = default;
};
class AuditLog {
public:
void check(Account& a) { a.balance_; } // OK: AuditLog is a friend
};
void freeze(Account& a) { a.balance_ = 0; } // OK: friend function
When to use friend
- Operator overloads needing private state (esp.
<<,==). - Tightly-coupled "buddy" classes — one is genuinely impl detail of the other (iterator + container).
- Hidden friend idiom (see § 5).
When NOT to use friend
- Workaround for poor encapsulation. Many classes needing access → wrong design; expose proper public interface.
- Avoiding accessor methods. Just write the accessors.
friendis not transitive (B's friend ≠ A's friend) and not inherited (friend ofBase≠ friend ofDerived).- Grants narrow, deliberate access.