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
A namespace is a named scope that 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 declared inside a namespace are accessed with namespace::name. The same namespace can be reopened multiple times across TUs — declarations from each are merged.
// math.h
namespace math {
double area(double r);
}
// shapes.h
namespace math { // reopens math, adds another declaration
double perimeter(double r);
}
The global namespace has no name; you can 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
An inline namespace's members are visible in the enclosing namespace as if they were 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
Inline namespaces let library authors evolve an ABI without breaking source compatibility — switch which version is inline to change the default while keeping older versions accessible.
3. using Declarations, Directives, and Aliases
Three different using constructs:
using declaration
Brings a single name into the current scope.
using std::cout;
using std::endl;
cout << "hi" << endl;
using directive
Brings all names from a namespace into the current scope. Convenient but pollutes the scope; 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 it inherits the dump, causing surprising name collisions.
Namespace alias
Shortens a long namespace name within a scope.
namespace fs = std::filesystem;
fs::path p = "/tmp";
Type alias
using (C++11) replaces typedef for type aliases, with cleaner syntax for 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 (also called Koenig lookup) is the rule that lets operator<< and friends work without qualification.
When you call an unqualified function, the compiler looks for it in:
- The usual scopes (current scope, enclosing scopes).
- The namespaces of the argument types — 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, you'd have to write ns::inspect(w) explicitly. ADL is what makes range-based for, swap, and 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
}
The "two-step" pattern (using std::swap; then unqualified swap) is the canonical way to invoke a swap that an end-user can customize for their own type via ADL. Note that ADL does not apply to fundamental-type arguments (their associated namespace set is empty), so a bare swap(v[0], v[1]) on a vector<int> would not find std::swap — you'd need either qualification or the two-step idiom.
Why this is sometimes surprising
ADL searches all argument-type namespaces, which 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
This is generally a feature (ADL = customization point), but it can cause unexpected overload picking. The standard library uses ADL deliberately for std::swap, std::begin, std::end, <<, etc.
5. The Hidden Friend Idiom
A "hidden friend" is a friend function defined inside a class body. It's only findable by ADL — not by ordinary name lookup. This is the modern idiomatic way to define 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 the class.
- Avoids the
template <typename T>boilerplate — no need to be a template.
Use hidden friends for binary operators (+, ==, <<, <=>) of value types.
6. friend Functions and Classes
friend declarations grant access to private and protected members of a class to a specific function or class outside of 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 that need private state (especially
<<and==). - Tightly-coupled "buddy" classes where one is genuinely an implementation detail of the other (e.g., iterator + container).
- The hidden friend idiom (see § 5).
When NOT to use friend
- As a workaround for poor encapsulation. If many classes need access, the design is wrong; expose a proper public interface.
- To avoid writing accessor methods. Just write the accessors.
friend is not transitive (B's friend isn't A's friend) and is not inherited (a friend of Base is not a friend of Derived). Friendship grants narrow, deliberate access.