Skip to content
C++
Language
since C++98
Basic

Namespaces

C++ namespaces — declarations, nesting, using directives, ADL, anonymous namespaces, inline namespaces for versioning, and namespace aliases.

Namespacesince C++98

A 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.

cpp
// 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

cpp
// 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

cpp
// 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

cpp
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.

cpp
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.

cpp
// 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.

cpp
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

cpp
// 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.

cpp
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.

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

See Also