Namespaces
C++ namespaces — declarations, nesting, using directives, ADL, anonymous namespaces, inline namespaces for versioning, and namespace aliases.
Namespacesince C++98A named declarative region that scopes identifiers to prevent name collisions across translation units and libraries, with zero runtime overhead.
Overview
Every symbol declared at file scope lives in the global namespace. Two independently developed libraries both defining Matrix, Error, or Logger collide silently in the global namespace. Wrapping declarations in a named namespace moves them one scope level down, requiring qualified access (ns::name) from outside — or an explicit using declaration.
Namespaces are a purely compile-time concept. There is no runtime representation, no virtual dispatch, no allocation. The cost is exactly zero.
Syntax
Declaration and Reopening
A namespace body can be opened and closed across multiple files. This is how the standard library works: each <header> adds more declarations to namespace std.
// geometry.h
namespace geometry {
struct Point { double x, y; };
double distance(Point a, Point b); // declaration
}
// geometry.cpp — reopen in a different translation unit
namespace geometry {
double distance(Point a, Point b) {
double dx = a.x - b.x, dy = a.y - b.y;
return std::sqrt(dx*dx + dy*dy);
}
}Nested Namespaces
// C++98: explicit nesting — verbose but always valid
namespace render {
namespace backend {
namespace vulkan { void init(); }
}
}
// C++17: compact nested namespace declaration
namespace render::backend::vulkan {
void init(); // identical to above
}
// C++20: inline specifier in compact form
namespace render::backend::inline v2 {
void init(); // v2 is inline within backend
}using Declarations vs. Directives
// using declaration — imports one specific name; participates in overload resolution
using std::vector;
using std::string;
vector<string> names; // ok
list<int> ids; // error: list not imported
// using directive — a hint to also search that namespace during unqualified lookup
// Names found via directive lose to names found by normal lookup.
// NEVER at file scope in headers. Acceptable in function scope.
void parse_config() {
using namespace std::chrono_literals; // C++14
auto timeout = 500ms;
auto delay = 2s;
}A using declaration is a name introducer. A using directive is a lookup hint. The distinction matters during overload resolution and name hiding.
Namespace Aliases
namespace fs = std::filesystem; // C++17
namespace chr = std::chrono;
namespace pmr = std::pmr; // C++17 (polymorphic memory resources)
fs::path log_dir{"/var/log/myapp"};
auto now = chr::steady_clock::now();
// Alias a deep internal namespace in .cpp files — not visible to callers
namespace impl = my::very::long::internal::detail;Argument-Dependent Lookup (ADL)
ADL (Koenig lookup) extends unqualified function lookup to include the namespaces associated with the types of each argument. It is not magic — it is a specified rule with large practical consequences.
namespace math {
struct Vec3 { float x, y, z; };
Vec3 operator+(Vec3 a, Vec3 b) noexcept { // noexcept: C++11
return {a.x+b.x, a.y+b.y, a.z+b.z};
}
float dot(Vec3 a, Vec3 b) noexcept {
return a.x*b.x + a.y*b.y + a.z*b.z;
}
std::ostream& operator<<(std::ostream& os, Vec3 v) {
return os << '(' << v.x << ',' << v.y << ',' << v.z << ')';
}
}
math::Vec3 u{1,0,0}, v{0,1,0};
auto w = u + v; // ADL: finds math::operator+
float d = dot(u, v); // ADL: finds math::dot — no qualification needed
std::cout << u; // ADL: finds math::operator<<For std::cout << u, the compiler searches math (for Vec3) and std (for ostream), finding math::operator<<. ADL also powers range-based for loops (since C++11): begin() and end() are found via ADL, which is why you can iterate over third-party containers by providing those functions in the container's namespace.
Library design rule: operators and customisation-point free functions must be defined in the same namespace as their primary type. Placing math::operator+ in the global namespace forces callers to qualify it explicitly — defeating the purpose.
Anonymous Namespaces
An unnamed namespace gives every member internal linkage — they are invisible to the linker, scoped to the translation unit. This is the correct modern idiom; it supersedes static at namespace scope.
// module_impl.cpp
namespace {
// Not exported; the linker never sees these symbols
constexpr int kMaxRetries = 3;
bool validate_key(int x) { return x > 0 && x < 1024; }
struct CacheEntry { // 'static' cannot give internal linkage to types
int key;
std::string value;
};
std::vector<CacheEntry> g_cache;
}
int lookup(int key) {
if (!validate_key(key)) return -1;
// g_cache search ...
}static at namespace scope only covers variables and functions. Anonymous namespaces cover all declarations — types, templates, constants — uniformly.
Inline Namespaces (C++11)
An inline namespace makes its members visible in the enclosing namespace without qualification. The canonical use case is library versioning with ABI stability.
namespace imglib {
inline namespace v3 { // C++11 — current, default version
struct ImageFormat { int bpp; int channels; int color_space; };
void encode(ImageFormat fmt, std::span<const std::byte> data); // std::span: C++20
}
namespace v2 { // previous API — still accessible explicitly
struct ImageFormat { int bpp; int channels; };
void encode(ImageFormat fmt, const unsigned char* data, std::size_t len);
}
}
// Unqualified access resolves to the inline (v3) version
imglib::ImageFormat fmt{8, 3, 0};
imglib::encode(fmt, pixel_data);
// Explicit access to old API — useful during migrations
imglib::v2::ImageFormat old{8, 3};
imglib::v2::encode(old, raw_ptr, byte_count);Inline namespaces affect name mangling: imglib::v2::encode and imglib::v3::encode produce different mangled symbols. A client compiled against v2 being inline and then linked against v3 gets a link error rather than silent ABI mismatch.
The std::literals sub-namespaces (std::string_literals, std::chrono_literals, etc., since C++14) are inline namespaces inside std. That is why using namespace std::chrono_literals works from within any using namespace std context.
Best Practices
// Headers: always qualify; never using namespace at file scope
// bad_header.h — NEVER
using namespace std; // every includer inherits this pollution
// good_header.h
#include <string_view> // C++17
std::vector<std::string> tokenise(std::string_view input);
// .cpp files: aliases and scoped directives are fine
namespace chr = std::chrono;
std::vector<std::string> tokenise(std::string_view input) {
using namespace std::string_literals; // scoped — harmless
if (input.empty()) return {"<empty>"s};
// ...
}
// ADL-enabling operators belong to the type's namespace
namespace geo {
struct Rect { int x, y, w, h; };
bool contains(Rect r, int px, int py); // ADL-friendly
std::ostream& operator<<(std::ostream&, Rect); // ADL-friendly
}
// File-local helpers: anonymous namespace, not static
namespace {
void internal_normalise(std::string& s);
}
// Versioned API: inline the current version
namespace mylib {
inline namespace v2 { struct Config { int version = 2; }; }
namespace v1 { struct Config { int version = 1; }; }
}Common Pitfalls
ADL Hijacking
Every namespace associated with an argument type is searched for unqualified calls — including calls the caller did not intend to dispatch via ADL.
namespace evil {
struct Hook {};
// This intercepts any unqualified swap() call involving evil::Hook
template<typename T>
void swap(T& a, T& b);
}
// Safe two-step swap idiom in generic code
template<typename T>
void my_algorithm(T& a, T& b) {
using std::swap; // bring std::swap as fallback
swap(a, b); // ADL first; falls back to std::swap if no better match
}
// Never call std::swap directly in templates if you want ADL-based customisation.
// Never call swap() unguarded if you want guaranteed std::swap.using namespace in Headers
A using namespace directive at file scope in a header injects those names into every translation unit that includes it — including transitive inclusions the library author never anticipated. It permanently narrows the namespace of every downstream user. Even using namespace std in a header is a defect.
Inline Namespace ABI Commitment
Switching which namespace is inline is an ABI break. Clients compiled with v2 inline link against v2-mangled symbols. Making v3 inline relinks cleanly only after recompiling all downstream consumers. Plan version promotions as part of release cycles, not ad hoc.
Qualified Lookup in Dependent Contexts
ADL does not apply to names in dependent base class templates. If a base provides a customisation-point function via ADL, templates inheriting from it must either qualify the name or introduce it with using.
template<typename T>
struct Base {
void hook();
};
template<typename T>
struct Derived : Base<T> {
void run() {
hook(); // error: not found in dependent base
this->hook(); // ok: deferred to instantiation
Base<T>::hook(); // ok: qualified
}
};Nested namespace shortcuts (C++17)
C++17 compact notation eliminates the visual indentation pyramid. Before C++17, deeply nested namespaces required matching closing braces for every level; the new :: separator collapses them onto one line.
// C++98 / C++11 / C++14 — three brace pairs, three closing braces
namespace corp {
namespace net {
namespace http {
void send(const char* url);
}
}
}
// C++17 — same effect, one declaration
namespace corp::net::http { // C++17
void send(const char* url);
}
// C++20 extends this: inline can appear on intermediate segments
namespace corp::net::inline v2 { // C++20
void send(const char* url); // corp::net::send resolves here by default
}Reopening still works: you can have multiple namespace corp::net::http { } blocks across different files. The compact form does not create a new kind of namespace; it is purely syntactic sugar.
Anonymous namespaces vs static
Both idioms give a symbol internal linkage, but anonymous namespaces are more powerful. static at namespace scope applies only to variables and functions; it cannot give internal linkage to types, enums, or templates.
// Using static — limited to objects and functions
static int counter = 0; // internal linkage
static void increment() { ++counter; }
// struct LocalState {}; // 'static' cannot apply to a type here
// Using anonymous namespace — covers everything
namespace {
int counter = 0; // internal linkage
void increment() { ++counter; }
struct LocalState { // types get internal linkage too
int calls = 0;
};
template<typename T> // templates too
T clamp(T v, T lo, T hi) {
return v < lo ? lo : v > hi ? hi : v;
}
LocalState g_state;
}Prefer anonymous namespaces in all new code. static at namespace scope is retained for C compatibility and legacy codebases; it is not deprecated but it is the older idiom.
Namespace aliases
Aliases let you write short local names without polluting the global scope. They are especially useful for long standard-library sub-namespaces and for insulating callers from deep internal paths.
namespace fs = std::filesystem; // C++17
namespace chr = std::chrono; // C++11
namespace pmr = std::pmr; // C++17
// Usage is identical to the full name
fs::path cfg_path = fs::current_path() / "config.json";
auto start = chr::steady_clock::now();
pmr::vector<int> buf{};
// Aliases are especially valuable in .cpp files for implementation namespaces
// that should not be exposed in headers.
namespace detail = my_lib::internal::impl::v3;
detail::WorkQueue q; // short and readableAn alias does not create a new namespace — it is just another name for the same scope. Redeclaring the same alias multiple times (including across headers that both define namespace fs = std::filesystem) is legal as long as both declarations refer to the same original namespace.
ADL (Argument-Dependent Lookup) and namespaces
ADL automatically searches the namespaces of all argument types when resolving an unqualified function call. This is what makes std::cout << value and swap(a, b) work without explicit qualification.
// C++11
namespace geo {
struct Point { double x, y; };
struct Segment { Point a, b; };
double length(Segment s) noexcept; // found via ADL
bool intersects(Segment s, Point p) noexcept; // found via ADL
std::ostream& operator<<(std::ostream&, Point); // found via ADL
}
geo::Segment seg{{0,0},{3,4}};
geo::Point pt {1,1};
double d = length(seg); // ADL: searches geo because seg is geo::Segment
bool b = intersects(seg, pt);// ADL: searches geo for both argument types
std::cout << seg.a; // ADL: searches geo (for Point) and std (for ostream)ADL also resolves begin() and end() in range-based for loops (C++11), enabling iteration over third-party containers without std::begin calls.
// C++11 — range-for finds begin/end via ADL
namespace audio {
struct Buffer { /* ... */ };
const float* begin(const Buffer& b) noexcept; // C++11
const float* end (const Buffer& b) noexcept; // C++11
}
audio::Buffer buf = load("kick.wav");
for (float sample : buf) { /* ADL finds audio::begin/end */ }Design rule: every operator and customisation-point free function must live in the same namespace as its primary type. Moving them to the global namespace forces callers to qualify every call and breaks generic code that relies on ADL.
Using declarations vs using directives
A using declaration imports exactly one name; a using directive asks the compiler to also search a whole namespace during unqualified lookup. The difference matters for overload resolution, name hiding, and header hygiene.
// using declaration — precise, participates in overload resolution normally
using std::sort; // C++98
using std::vector; // C++98
sort(v.begin(), v.end()); // resolves to std::sort directly
// using directive — a hint; names found via directive lose to names in
// the current scope (and are ambiguous with other directive-found names)
using namespace std; // C++98
// Ambiguity example
namespace A { void foo(int); }
namespace B { void foo(double); }
void bar() {
using namespace A;
using namespace B;
foo(1); // error: ambiguous between A::foo and B::foo
}
// using declaration avoids ambiguity
void baz() {
using A::foo; // only A::foo
foo(1); // unambiguous
}// Scoped directives in function bodies are acceptable — the scope limits the damage
// C++14
void process_duration() {
using namespace std::chrono_literals; // C++14
auto timeout = 200ms;
auto retry = 1s;
}
// Never at file scope in a header — this poisons every includer:
// using namespace std; // BAD in any headerPrefer using declarations everywhere. Reserve using directives for narrowly scoped convenience in .cpp function bodies (e.g., using namespace std::chrono_literals).
Namespace versioning for ABI stability
Inline namespaces are the standard mechanism for evolving a library's ABI without breaking existing binaries. Making one version inline causes its symbols to be the default; older versions remain accessible via explicit qualification.
// C++11
namespace imglib {
namespace v1 {
struct Codec { int channels; };
void encode(Codec c, const unsigned char* data, std::size_t len);
}
namespace v2 {
struct Codec { int channels; int color_space; };
void encode(Codec c, const unsigned char* data, std::size_t len);
}
inline namespace v3 { // C++11 — v3 is the current default
struct Codec { int channels; int color_space; int bit_depth; };
void encode(Codec c, std::span<const std::byte> data); // C++20
}
}
// Unqualified access resolves to the inline version
imglib::Codec c{3, 0, 8};
imglib::encode(c, pixel_data);
// Explicit access for migration or binary compatibility
imglib::v2::Codec old{3, 1};
imglib::v2::encode(old, raw, raw_len);Because name mangling incorporates the full namespace path, imglib::v2::encode and imglib::v3::encode produce different mangled symbols. A translation unit compiled against v2 as inline and then linked against a shared object where v3 is inline gets a link error — not a silent wrong-version call. This is a feature.
ABI commitment: once you ship a release with vN as the inline version, changing which namespace is inline is an ABI break for any downstream binary that was not recompiled. Treat inline-namespace promotions as semver-major events.
std namespace: what you can and cannot add
The C++ standard defines exactly which additions to namespace std are legal; violating this rule is undefined behaviour. The rules are stricter than most developers expect.
// LEGAL: explicit specialisation of a standard template for a user-defined type
// C++11
#include <functional>
struct MyKey { int id; std::string tag; };
template<>
struct std::hash<MyKey> { // C++11 — explicit specialisation
std::size_t operator()(const MyKey& k) const noexcept {
return std::hash<int>{}(k.id) ^
(std::hash<std::string>{}(k.tag) << 1);
}
};
std::unordered_map<MyKey, int> table; // works because hash<MyKey> is found
// LEGAL: specialisation of std::swap is deprecated in C++20 in favour of
// a hidden-friend swap; the recommended modern idiom is a hidden friend:
struct Widget {
int value;
friend void swap(Widget& a, Widget& b) noexcept { // C++11 hidden friend
using std::swap;
swap(a.value, b.value);
}
};// ILLEGAL — adding declarations to std (undefined behaviour)
namespace std {
void my_utility(); // UB: adding new name
template<typename T>
struct my_container {}; // UB: adding new template
}
// ILLEGAL — specialising standard templates for non-user-defined types
template<>
struct std::hash<int*> { /* ... */ }; // UB: int* is not a user-defined typeThe only additions permitted by the standard are: (1) explicit specialisations of standard class templates for program-defined types, and (2) explicit specialisations of standard function templates that do not depend on a non-user-defined type. Everything else is undefined behaviour per [namespace.std].