Exercises — Inline namespaces, anonymous namespaces
Two words we will lean on repeatedly, defined once so no symbol is unearned:
Level 1 — Recognition
Exercise 1.1
What linkage do the names inside an anonymous namespace have?
Recall Solution
Internal linkage. Every name declared inside an unnamed namespace is visible only within the TU that declared it. The compiler behaves as if it invented a unique hidden name per TU plus an implicit using directive, so two different .cpp files can never see each other's copies.
Exercise 1.2
In the code below, which single word makes process reachable as lib::process()?
namespace lib {
inline namespace v2 { void process(); }
}Recall Solution
The keyword ==inline==. Because v2 is an inline namespace, its members leak up into the enclosing namespace lib, so lib::process() resolves to lib::v2::process().
Exercise 1.3
True or false: an anonymous namespace is a good place to declare something inside a header file.
Recall Solution
False. A header is #included into many TUs; each inclusion produces a separate internal-linkage copy. See Exercise 2.2 for the concrete damage this causes.
Level 2 — Application
Exercise 2.1
Two files each define a file-local helper. Will they link?
// a.cpp
namespace { int compute() { return 1; } }
// b.cpp
namespace { int compute() { return 2; } }Recall Solution
Yes, they link cleanly. Each compute sits in a different invented unnamed namespace (one per TU), so the linker sees two distinct symbols — no clash, no violation of the One Definition Rule. Had both been written as global int compute() with external linkage, the linker would report "multiple definition of compute."
Exercise 2.2
You put namespace { int counter = 0; } in util.h and include it in three .cpp files, expecting one shared counter. How many independent counter objects actually exist, and why?
Recall Solution
Three independent objects — one per including TU. The unnamed namespace forces internal linkage, and internal linkage means each TU gets its own private copy. Incrementing the counter in one file does nothing to the others, and the binary carries three separate variables. Fix: keep the shared counter with external linkage — declare extern int counter; in the header and define it in exactly one .cpp.
Exercise 2.3
Given the versioned library below, what does each call resolve to?
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);
double b = mathx::v1::area(2.0);Recall Solution
mathx::area(2.0)→ the inlinev2version, sincev2leaks intomathx. Value: .mathx::v1::area(2.0)→ the explicit legacyv1. Value: . Default callers float forward tov2for free; anyone who pinnedv1is untouched.

Level 3 — Analysis
Exercise 3.1
Explain, in terms of what the compiler conceptually inserts, why an unnamed namespace can never collide across files, whereas a global name might.
Recall Solution
For each TU the compiler acts as if it wrote:
namespace __unique_per_TU { /* your names */ }
using namespace __unique_per_TU; // implicitThe invented name __unique_per_TU differs in every .cpp. So secret in file A is really __uniqueA::secret and in file B is __uniqueB::secret — different fully-qualified names ⇒ different linker symbols ⇒ no collision. A global name has one shared fully-qualified spelling and external linkage, so two definitions map to the same symbol and the linker rejects the duplicate.
Exercise 3.2
Does unqualified draw(w) compile below? Justify using Argument-Dependent Lookup (ADL).
namespace ns {
inline namespace impl { struct Widget {}; void draw(Widget){} }
}
ns::Widget w;
draw(w); // no ns:: prefixRecall Solution
Yes. ADL says: to resolve an unqualified function call, also search the namespaces associated with the argument types. Widget's associated namespace is ns::impl. Because impl is inline, its contents are treated as members of ns too, so the associated set effectively includes ns, and draw (which lives in impl/ns) is found. Had impl been a plain (non-inline) namespace, draw would still be found via ADL because it's in the same associated namespace as Widget — the inline keyword's real payoff shows up when you call ns::draw(w) qualified, which only works because inline leaks draw up into ns.
Exercise 3.3
Why does moving the inline keyword from v2 to v3 correctly re-route both source-level calls and linker symbols, without touching caller code?
Recall Solution
Two layers change together:
- Source resolution: the unqualified name
mathx::areanow leaks up from whichever namespace is inline. Moveinlinetov3and everymathx::areacall recompiles tov3::area— a one-keyword diff, no caller edits. - Linker symbols (ABI): the inline namespace name is baked into the mangled symbol, e.g.
_ZN5mathx2v34areaEdvs_ZN5mathx2v24areaEd. So an old object file that was compiled againstv2keeps referencing thev2symbol; it does not silently start callingv3. Version is encoded end-to-end.
Level 4 — Synthesis
Exercise 4.1
Design a library net that ships an experimental v3 while v2 stays the default, and a stable v1 remains callable. Write the namespace skeleton and state what each of these resolves to: net::send(), net::v3::send(), net::v1::send().
Recall Solution
namespace net {
namespace v1 { void send(); } // legacy, opt-in
inline namespace v2 { void send(); } // current DEFAULT
namespace v3 { void send(); } // experimental, opt-in
}net::send()→net::v2::send()(onlyv2is inline, so only it leaks up).net::v3::send()→ the experimental version, reachable only by explicit qualification.net::v1::send()→ the legacy version, explicit. Whenv3is ready to become default you moveinlinefromv2tov3; existingnet::send()calls migrate on recompile, pinned callers ofv1/v2are unaffected.
Exercise 4.2
You have a header-based, single-file library where every symbol must be file-local to avoid clashing with the host program, including a helper struct Node. Explain why you cannot use static for Node, and write the correct construct.
Recall Solution
static grants internal linkage only to variables and functions; a type like struct Node has no linkage to make static in this sense — the keyword simply isn't allowed to give a class internal linkage. The uniform tool is the unnamed namespace, which gives internal linkage to everything, types included:
// helper.cpp (NOT a header — anonymous namespaces stay in .cpp)
namespace {
struct Node { int value; };
Node makeNode(int v) { return Node{v}; }
}Level 5 — Mastery
Exercise 5.1
A team writes namespace { int cache; } in a widely-included header and reports two symptoms: (a) the executable is unexpectedly large, and (b) writes to cache in one file are invisible in another. Diagnose both symptoms with a single root cause, then give the corrected design.
Recall Solution
Root cause: internal linkage × N inclusions. The unnamed namespace makes cache internal-linkage, and the header is included by, say, N = 5 TUs. Result: 5 independent cache objects.
- Symptom (a), bloat: 5 separate variables (and any functions in that namespace) are emitted, one per TU.
- Symptom (b), no sharing: each TU reads/writes its own copy, so cross-file communication silently fails. Corrected design — one shared object with external linkage:
// cache.h
extern int cache; // declaration only
// cache.cpp
int cache = 0; // single definition, external linkageNow exactly one cache exists and all TUs share it. Rule of thumb: anonymous namespaces live in .cpp files; shared state lives behind an extern declaration + one definition.
Exercise 5.2
Predict the printed output. Then explain which mechanism (inline leak, ADL, or per-TU uniqueness) each line exercises.
namespace app {
namespace v1 { int scale(int x){ return x * 2; } }
inline namespace v2 { int scale(int x){ return x * 10; } }
inline namespace v2 { struct Vec { int n; }; int norm(Vec v){ return v.n; } }
}
int main() {
app::Vec v{7};
std::cout << app::scale(3) << "\n"; // line A
std::cout << app::v1::scale(3) << "\n"; // line B
std::cout << norm(v) << "\n"; // line C (no app::)
}Recall Solution
Output:
30
6
7
- Line A → 30.
app::scaleresolves through the inline leak tov2::scale, so . - Line B → 6. Explicit
app::v1::scaleopts into legacy: . - Line C → 7.
norm(v)is unqualified.Vec's associated namespace isapp::v2, which is inline (so alsoapp); ADL findsnorm. It returnsv.n = 7.
Recall Master checklist
Anonymous namespace gives which linkage? ::: Internal — per-TU private.
Where must anonymous namespaces never appear? ::: In headers (each include = a separate copy).
lib::process() with inline namespace v2 resolves to? ::: lib::v2::process().
To promote a new default version you move which keyword? ::: inline.
Does the inline namespace name enter the mangled symbol? ::: Yes — it is ABI-tagged.
Can static give a struct internal linkage? ::: No; use an unnamed namespace instead.