Visual walkthrough — Inline namespaces, anonymous namespaces
This page is a companion to Inline namespaces, anonymous namespaces. It leans on Namespaces (basics), Translation units and the One Definition Rule, and Internal vs external linkage.
Step 1 — What is a "name" once compiling starts?
WHAT: We split the world into two rooms — the compiler (works on one file) and the linker (glues all the finished files together).
WHY: Every rule about namespaces is really a rule about which room can see which box. If we don't picture the two rooms, the rules look like arbitrary magic.
PICTURE: Look at Step 1. Two files each become a TU; each TU produces boxes; the linker at the bottom collects every box into one table.

Step 2 — Linkage: which boxes are visible to the linker?
WHAT: We tag every box as "external" (public label) or "internal" (private label).
WHY: A One Definition Rule violation — the dreaded "multiple definition" link error — happens only when two external boxes share the same label. Internal boxes can never collide, because the linker never even sees them.
PICTURE: In Step 2, external boxes have a solid orange handle reaching into the shared table; internal boxes have a teal padlock and no handle.
x— a plain global: its label goes public, so two files definingxclash.y— markedstaticat file scope: label stays private, so two files each withstatic int yare fine.

Step 3 — The anonymous namespace = an auto-generated private room
WHAT: We write a namespace with no name and drop our helper inside it.
namespace { // no name
int secret = 42;
}WHY: We want the internal-linkage effect of static, but for anything — variables, functions, types, templates. static cannot make a struct file-local; the unnamed namespace can.
HOW (the mental rewrite): The compiler treats the code above as if you wrote a namespace with a secret, per-file, one-of-a-kind name, then immediately pulled it into scope:
__TU7a— an invented label different in every.cpp. Because file A's is__TU7aand file B's is__TU9k, theirsecrets have different mangled symbols (see Name mangling and the ABI) and can never clash.using namespace __TU7a;— implicitly added so you can still just typesecretwithout qualification inside this file.
PICTURE: Step 3 shows two files; each grows its own uniquely-labeled private room around secret. The two rooms never touch.

Step 4 — Edge case: the same helper name in two files
WHAT: Both logger.cpp and metrics.cpp define const char* tag.
WHY: This is exactly the collision Step 2 warned about — unless we wrap each in an anonymous namespace.
PICTURE: Step 4 is a side-by-side. Left: bare globals → two identical labels hit the linker table → red CLASH. Right: each wrapped in its unnamed namespace → two distinct mangled labels → green OK.
The two mangled strings differ in the invented middle piece, so the linker files them as two unrelated boxes.

Step 5 — Now the opposite goal: we WANT a name to escape
WHAT: Instead of hiding, we want lib::v2::process to also answer to the shorter name lib::process.
WHY: API versioning. Customers wrote lib::process(). We want their code to float forward to the newest version with zero edits, while a customer who pinned lib::v1::process() keeps the old behavior.
namespace lib {
inline namespace v2 { void process(); } // current default
namespace v1 { void process(); } // opt-in legacy
}PICTURE: Step 5 shows the lib room containing two sub-rooms, v1 (closed door) and v2 (open archway labeled inline). Through the archway, process is also visible on the lib wall.

Step 6 — What inline actually does to lookup
WHAT: We trace three ways a caller can write the name and where each lands.
| Caller writes | Resolves to | WHY |
|---|---|---|
lib::process() |
lib::v2::process() |
v2 is inline → its members leak into lib |
lib::v2::process() |
lib::v2::process() |
explicit, same box |
lib::v1::process() |
lib::v1::process() |
explicit opt-in; v1 is not inline |
WHY it beats a plain using: A using namespace v2; would make the name visible, but lib::v2::T and lib::T would stay different entities for template specialization and for the mangled symbol. inline fuses them, and the version gets baked into the linker symbol (_ZN3lib2v17processEv) — see Name mangling and the ABI.
PICTURE: Step 6 is a lookup flow: the query lib::process walks into lib, sees the inline archway, and arrives at v2::process; the same box carries a mangled label with 2v2 embedded.

Step 7 — Edge case: ADL sees through the archway
WHAT: An unqualified call draw(w) where w's type lives in an inline namespace.
namespace ns {
inline namespace impl { struct Widget {}; void draw(Widget){} }
}
ns::Widget w;
draw(w); // found — no ns:: neededWHY: Argument-Dependent Lookup (ADL) finds functions in the associated namespaces of an argument's type. Because impl is inline, Widget's associated namespace effectively includes ns, so draw is reachable as if it lived directly in ns.
PICTURE: Step 7: the argument w sends dotted "search here" rays into both impl and — through the inline archway — into ns, catching draw.

Step 8 — Edge case: two libraries, both inline namespace v1
WHAT: LibFoo and LibBar each ship inline namespace v1. Do they merge?
WHY: People fear silent mixing. But the inline name is part of the mangled symbol, and the two libraries live in different enclosing namespaces (foo::v1::... vs bar::v1::...), so their symbols differ. Even same-library, different-version objects produce different mangled symbols → the linker keeps them apart or fails loudly instead of silently calling the wrong code. That is the intended ABI-tagging (see static keyword (storage & linkage) for the older, coarser tool this replaces).
PICTURE: Step 8: two symbol strings side by side, the differing version segment highlighted in plum — proof they never collapse into one box.

The one-picture summary
Everything compresses to a single question at each box: does its label leave the file, and if so, under what name?

- Anonymous namespace → padlock: label wrapped in a per-file secret name, never reaches the shared table → no collisions possible.
- Plain namespace → labeled box in the shared table under its full path.
- Inline namespace → labeled box plus a shortcut arrow: the enclosing name works too, and the version is stamped into the symbol.
Recall Feynman retelling of the whole walkthrough
Every file is a room. When a room finishes, it hands the linker a stack of boxes with names on them. Two rooms handing over boxes with the same name causes a fight (a link error).
An anonymous namespace is a private drawer inside one room: the compiler secretly renames everything in it with that room's unique tag, so even if two rooms both keep a box called "tag," their real names differ and no fight ever happens. (Never put that drawer in a header, because the header is copied into every room, giving each its own separate drawer.)
An inline namespace is the opposite wish: you have a box in a labeled cupboard v2 inside the big cupboard lib, and you leave the v2 door propped open so the box also answers to the plain lib name. New visitors who just ask for lib::process walk straight to the newest version; old visitors who insist on lib::v1::process still find the old one behind its closed door. And because the version is written into the box's real name, boxes from different versions never get confused — that is the safety feature, not a bug.
Recall
Empty label test
One-keyword release
inline keyword to it.