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
}
};