Intuition What this page is for
The parent note told you what the two namespace flavors do. This page walks you through every situation you can hit in real code — every combination of "named vs unnamed", "inline vs plain", "one file vs many files", "does the linker complain?", "which version gets called?". If you meet a scenario in an exam or a real bug, one of the cells below already showed it to you.
First we lay out the full grid of cases. Then each worked example is tagged with the exact cell it covers.
Definition ODR — the One Definition Rule
Before we start: ODR stands for the One Definition Rule . In plain words it says a whole program may contain at most one definition of any given external entity (a function or variable that is visible across files). Break it and the linker complains — or worse, silently misbehaves. We lean on this rule throughout, so it gets its own name here. See Translation units and the ODR .
Think of every namespace question as a point on a grid. The two axes that matter most are:
Linkage axis — does a name escape its .cpp file? (This is the internal vs external linkage question.)
Visibility / versioning axis — from where can you write the name , and which definition wins?
Cell
Scenario class
Key question
Example
C1
Global name in two .cpp files
Does the linker collide?
Ex 1
C2
Anonymous namespace in two .cpp files
Same name, no collision?
Ex 2
C3
Type/template in anonymous namespace
Does the "not just functions" claim hold?
Ex 3
C4
Anonymous namespace in a header (degenerate/wrong use)
What breaks, and how much?
Ex 4
C5
Inline namespace, unqualified call
Which version is default?
Ex 5
C6
Inline namespace, explicit v1 call (opt-out)
Can old callers still pin the old code?
Ex 5
C7
Moving inline to a new version (limiting case: releasing v3)
One-keyword migration
Ex 6
C8
[[Argument-Dependent Lookup (ADL)
ADL]] through an inline namespace
Is the name found unqualified?
C9
Mangled symbol / ABI (real-world link failure)
Does the version bake into the symbol?
Ex 8
C10
Exam twist: inline namespace vs inline function
Same word, different mechanism
Ex 9
C11
Inline namespace in a header across many TUs
Does identical inline definition break ODR?
Ex 10
Recall The two axes in one sentence
Anonymous = internal linkage (the linkage axis) ::: it decides whether a name escapes its translation unit.
Inline = visibility + versioning (the lookup/ABI axis) ::: it decides which definition is the unqualified default and what the symbol is called.
The figure below draws this whole grid as a picture. Read it now: the coral boxes on the lower row are linkage-axis failures (Ex 1, and the ABI trap Ex 8); the mint / butter boxes are the "no problem, this is the fix" cells (Ex 2, Ex 3, Ex 9); the lavender boxes up the vertical axis are visibility / versioning cells (Ex 5–7). The white note in the middle is the single decision rule to memorize: ask which axis a bug lives on first. Every worked example below is one labelled box in this picture, so you can always locate where you are.
Two source files each define a global at file scope:
// logger.cpp
const char* tag = "LOG" ;
// metrics.cpp
const char* tag = "MET" ;
Both are compiled and linked into one program. What does the linker do?
Forecast: guess before reading — does this link cleanly, or fail? If it fails, with what message?
Steps
Here tag is a mutable pointer (const char* means "pointer to const char", not "const pointer"), so it is an ordinary namespace-scope variable with external linkage — its symbol is exported.
Why this step? Whether a name escapes the file is decided by its linkage . A plain (non-const-object, non-static) namespace-scope variable has external linkage → its symbol is exported.
Both files export a symbol named tag. At link time the linker sees two definitions of the same external symbol .
Why this step? This is exactly the One Definition Rule (ODR) , defined at the top of this page: a program may contain at most one definition of any external entity.
Result: a link error , multiple definition of 'tag'.
Why this step? The linker cannot pick one — that would silently drop code.
Verify: Count exported symbols named tag: file A exports 1, file B exports 1, total 2, allowed maximum 1. Since 2 > 1 , the ODR is violated → link fails. ✅
Now wrap each global in an unnamed namespace:
// logger.cpp
namespace { const char* tag = "LOG" ; }
// metrics.cpp
namespace { const char* tag = "MET" ; }
Does it link now?
Forecast: same name in both files — surely still a clash? Guess yes/no.
Steps
The compiler rewrites each unnamed namespace as if it had a unique hidden name per file , plus an implicit using:
// logger.cpp becomes:
namespace __anon_logger { const char* tag = "LOG" ; }
using namespace __anon_logger ;
Why this step? That invented name is different in every translation unit, which is the whole mechanism.
So the two symbols are really __anon_logger::tag and __anon_metrics::tag — distinct names .
Why this step? Distinct names can't collide, no matter what you typed in source.
Each gets internal linkage : invisible outside its own .cpp.
Why this step? Internal linkage means the symbol is never offered to the linker for cross-file matching.
Verify: Exported symbols named exactly tag: file A exports 0 (internal), file B exports 0 (internal). 0 ≤ 1 → ODR satisfied → link succeeds. Both tags coexist. ✅
Make a helper type file-local:
// cache.cpp
namespace { struct Cache { int n; }; }
int useCache () { Cache c{ 7 }; return c.n; }
Could you have written static struct Cache {...}; instead? What is the difference?
Forecast: guess whether static can give a type internal linkage.
Steps
static on a namespace-scope declaration gives internal linkage only to variables and functions , not to type definitions.
Why this step? The grammar of static simply doesn't apply the internal-linkage rule to struct/class/enum/template definitions. (See static keyword .)
The unnamed namespace, being a namespace , gives internal linkage to everything declared inside — variables, functions, and types.
Why this step? This uniformity is exactly why modern C++ prefers the unnamed namespace over static.
useCache() returns c.n where c.n == 7.
Why this step? We aggregate-initialize Cache{7}, so the member n holds 7.
Verify: Cache{7}.n → the single member n is set to 7, so useCache() returns 7 . ✅
A developer puts this in config.h:
// config.h
namespace { int counter = 0 ; }
inline void bump () { ++ counter; }
Files a.cpp and b.cpp both #include "config.h" and both call bump(). After a.cpp calls bump() 3 times and b.cpp calls it 2 times, what is "the" counter?
Forecast: guess the final value if you thought it was one shared counter, vs the value it actually holds.
Steps
#include textually pastes the header into each .cpp. So each translation unit gets its own unnamed namespace, hence its own separate counter.
Why this step? Internal linkage means "one independent copy per TU" — the copies are not shared.
a.cpp's counter goes 0 → 3 . b.cpp's counter goes 0 → 2 . They are two different variables.
Why this step? The naive mental model ("counter == 5") is wrong precisely because of internal linkage.
There is no single value 5 anywhere. The bug is silent: no compiler error, just broken shared-state assumptions plus binary bloat.
Why this step? This is the classic "keep anonymous namespaces in .cpp files only" rule.
Verify: Two independent counters: TU-a = 3 , TU-b = 2 . Sum 3 + 2 = 5 is what you expected ; but the reachable values are { 3 , 2 } , never a shared 5 . So the assertion "there exist two counters with values 3 and 2, and no shared 5" holds. ✅
The versioned math library:
namespace mathx {
namespace v1 { double area ( double r ){ return 3.14 * r * r; } }
inline namespace v2 { double area ( double r ){ return 3.14159 * r * r; } }
}
double a = mathx :: area ( 2.0 ); // (A) unqualified default
double b = mathx :: v1 :: area ( 2.0 ); // (B) explicit legacy
Compute a and b.
Forecast: which value does the unqualified mathx::area produce — the 3.14 one or the 3.14159 one?
Steps
v2 is declared inline, so its names leak into mathx . mathx::area therefore resolves to mathx::v2::area (cell C5).
Why this step? Inline namespaces make the enclosing namespace see their members without qualification — that is the versioning mechanism.
a = mathx::v2::area(2.0) = 3.14159 \cdot 2^2 = 3.14159 \cdot 4.
Why this step? Plug r = 2 into the v2 formula.
mathx::v1::area is fully qualified, so it bypasses the default and picks the old code (cell C6).
Why this step? Explicit qualification always wins — old callers who pinned v1 keep working.
b = 3.14 \cdot 2^2 = 3.14 \cdot 4.
Why this step? We evaluate the v1 formula at r = 2 so we can compare it against the v2 answer from step 2 and confirm the two versions really do differ numerically.
Verify: a = 3.14159 × 4 = 12.56636 , b = 3.14 × 4 = 12.56 . And a > b because v2 is more precise (larger π approximation). ✅ (See API versioning .)
A year later you ship v3. You want new callers to get v3 automatically while v1 and v2 stay callable. Show the one-keyword change and confirm what mathx::area(2.0) now returns.
namespace mathx {
namespace v1 { double area ( double r ){ return 3.14 * r * r; } }
namespace v2 { double area ( double r ){ return 3.14159 * r * r; } } // was inline
inline namespace v3 { double area ( double r ){ return 3.14159265 * r * r; } }
}
Forecast: after the change, does unqualified mathx::area(2.0) still call v2, or v3?
Steps
Remove inline from v2, add inline to v3.
Why this step? Only one namespace should be the inline default; the default "floats" to wherever the keyword sits.
mathx::area now resolves to mathx::v3::area.
Why this step? v3's names now leak into mathx.
mathx::area(2.0) = 3.14159265 \cdot 4.
Why this step? Plug r = 2 into the v3 formula.
mathx::v2::area(2.0) and mathx::v1::area(2.0) still compile and call their own code by full name — nobody's code broke.
Why this step? Removing inline only changes what the unqualified default is; the older namespaces still exist as ordinary named namespaces, so any caller who wrote the full path is completely unaffected — that is what makes the migration a safe one-keyword diff.
Verify: New default = 3.14159265 × 4 = 12.5663706 . It is strictly larger than the old default 12.56636 , confirming the default moved forward to v3. ✅
namespace ns {
inline namespace impl {
struct Widget {};
void draw ( Widget ){ /* ... */ }
}
}
ns ::Widget w;
draw (w); // unqualified — does this compile?
Forecast: draw is written with no namespace prefix. Guess whether the compiler finds it.
Steps
Unqualified draw(w) triggers ADL : the compiler collects the associated namespaces of the argument's type Widget.
Why this step? ADL is the rule that lets you call free functions like draw without qualifying them, by searching the type's home namespace.
Widget lives in ns::impl, but because impl is inline , its associated namespaces effectively include the enclosing ns as well.
Why this step? "Inline" means lookup and ADL "see through" the inner namespace as if members were in ns.
draw is found in that associated set → the call compiles and calls ns::impl::draw.
Why this step? Once ADL has the right namespace, the matching overload is selected.
Verify: Associated namespaces of Widget = { ns::impl } ∪ { ns } (inline pull-up). draw ∈ that set → found. So the boolean "draw resolvable via ADL" is true. ✅ (This figure shows the lookup path.)
libgraph.so is built with inline namespace v1. An app is later re-linked against a rebuilt library that now uses inline namespace v2 for the same function process(). The old object file references the v1 symbol.
// symbol emitted for lib::v1::process() -> _ZN3lib2v17processEv
// symbol emitted for lib::v2::process() -> _ZN3lib2v27processEv
What happens at link time, and why is this a feature?
Forecast: guess whether the old object silently binds to the new v2 code, or fails to link.
Steps
The inline namespace name is part of the mangled symbol : v1 → 2v1, v2 → 2v2 in the encoded name.
Why this step? Mangling encodes namespaces so different versions get different linker symbols.
The old object still asks for _ZN3lib2v17processEv; the new library only provides _ZN3lib2v27processEv.
Why this step? The two encoded strings differ (2v1 ≠ 2v2), so they are not the same symbol.
The linker reports an unresolved symbol rather than silently calling the wrong version.
Why this step? Loud failure beats a silent ABI mismatch calling incompatible code.
Verify: Compare mangled strings: _ZN3lib2v17processEv vs _ZN3lib2v27processEv. They differ at the v1/v2 position, so they are distinct symbols → the old reference is unresolved. Boolean "symbols equal" is false, i.e. the link correctly fails instead of silently mixing versions. ✅
True or false, and explain: "Adding inline to a namespace makes its functions faster the way inline on a function does."
Forecast: guess T/F before reasoning.
Steps
inline on a function is an ODR / linkage relaxation: it lets a definition appear in a header included by many TUs without a multiple-definition error, and hints the optimizer may inline the body.
Why this step? The classic meaning of the keyword — about definitions and linkage .
inline on a namespace is about name visibility and versioning — it has nothing to do with call speed or code inlining.
Why this step? Same spelled keyword, entirely different mechanism; conflating them is the trap.
Therefore the statement is False .
Why this step? We answer the true/false prompt explicitly so the exam-style claim is resolved.
Verify: The claim conflates two unrelated meanings of one keyword → the statement is false. Boolean = False. ✅
Unlike the anonymous -namespace-in-a-header disaster of Ex 4, putting a named/inline namespace in a header is the normal, correct way to ship a library. Consider:
// shape.h (included by a.cpp AND b.cpp)
namespace shape {
inline namespace v1 {
inline double area ( double r ){ return 3.14159 * r * r; } // note: inline FUNCTION too
}
}
Both a.cpp and b.cpp #include "shape.h" and call shape::area(...). Does linking these two TUs violate the ODR the way Ex 1 did?
Forecast: two files, both containing the same definition of area — collision like Ex 1, or fine? Guess before reading.
Steps
The inline namespace has external linkage (it is named , not anonymous), so shape::v1::area gets one shared, exported symbol — not a private per-file copy.
Why this step? This is the exact opposite of Ex 4: anonymous → many internal copies; named/inline → one external entity. It is the difference between the two axes of the matrix.
Because both TUs see the identical token-for-token definition (same header, same inline function body), the ODR grants its special exception: an inline entity may be defined in every TU, provided all definitions are identical.
Why this step? This is why library authors may put definitions in headers — the inline keyword on the function is what licenses the repeated identical definition without a "multiple definition" error.
The linker merges the identical definitions into one shape::v1::area and shape::area resolves to it everywhere.
Why this step? One merged symbol means a.cpp and b.cpp genuinely call the same code — the shared-state assumption that Ex 4 broke now holds.
Verify: Distinct exported definitions of shape::v1::area after merge = 1 (identical inline definitions collapse to one). Since 1 ≤ 1 , ODR is satisfied → link succeeds, and it is the same entity in both files — the mirror image of Ex 4. ✅
Common mistake Ex 4 vs Ex 10 — don't confuse them
Anonymous namespace in a header → many independent internal copies → broken shared state (Ex 4, wrong). Named/inline namespace with inline functions in a header → one shared external entity via the ODR inline exception → correct library shipping (Ex 10). The word "header" is the same; the linkage is opposite.
Common mistake Meta-mistake across all cells
Mixing up the two axes. If the bug is "won't compile / won't link across files," you are on the linkage axis → think anonymous / static / external linkage . If the bug is "the wrong version got called" or "which name is default," you are on the visibility axis → think inline namespace . Ask which axis first; half the confusion vanishes.
Recall Scenario self-test
Two files with a plain global int x; — link error or fine? → link error (ODR, both external)
Same two files but namespace { int x; } each — ? → fine, distinct internal symbols
Header with namespace { int c; }, included in 2 TUs — how many c? → two independent copies
Header with inline namespace v1 { inline double area(...); }, included in 2 TUs — how many area? → one shared entity (identical inline definitions merge, ODR ok)
inline namespace v2 present, caller writes lib::f() — which version? → v2 (the inline default)
Move inline from v2 to v3 — unqualified callers now get? → v3
Does v1 vs v2 change the mangled linker symbol? → yes
inline on a namespace affects runtime speed? → no; visibility/versioning only