Lesson 13 / 14
Namespaces and Code Organization
The measurement of how two symbols with the same name are separated by namespaces, and that a using declaration and a namespace alias are not a new symbol but an access path to an existing one.
Published Updated
In this lesson
- Measures that a symbol in a different namespace with the same name always reaches its own definition through a qualified name
- Shows that a using declaration does not establish a new symbol, but opens a local access path to an existing symbol
- Verifies that two different access paths (full name, alias) to a nested namespace always reach the same variable
Contents
The previous lesson showed that ErrorObject::copy_count, a static member defined
in a single translation unit, shared the same counter across every call — the
measurement was within class and object boundaries. This lesson moves the boundary up
a level: namespaces. Two different libraries or modules may want to use the same
name (compute, Vehicle); namespaces resolve this collision by placing each symbol
inside its own namespace. This is a different axis from the “which object does this
work with” question measured since the start of the course: here it is not the object
but the symbol itself — which definition a name corresponds to — that is measured.
The measurement question follows a similar pattern — which symbol does an expression
work with — but now it is not a class or an object being measured, but a name and the
namespace holding it. In terms of code organization, the practical consequence is
simple: a library groups related names under a single namespace, both preventing
collisions and making it clear to the reader where those names come from.
Two Different Symbols With the Same Name: Namespaces Separate Them
Both the land and sea namespaces define a function named compute. When called by
qualified name (land::compute, sea::compute), are these really two different
functions, or do they somehow resolve to the same symbol?
// namespace_same_name.cpp — namespaces separate two different symbols with the same name
#include <iostream>
namespace land {
int compute(int base) {
return base * 2;
}
}
namespace sea {
int compute(int base) {
return base * 3;
}
}
int main() {
std::cout << "land::compute(5) result " << land::compute(5) << "\n";
std::cout << "sea::compute(5) result " << sea::compute(5) << "\n";
int (*land_ptr)(int) = land::compute;
int (*sea_ptr)(int) = sea::compute;
std::cout << std::boolalpha
<< "are land::compute and sea::compute the same function "
<< (land_ptr == sea_ptr) << "\n";
return 0;
}
land::compute(5) result 10 sea::compute(5) result 15 are land::compute and sea::compute the same function false
PS16. land::compute(5) and sea::compute(5) give different results (10 and 15) —
same name, two independent bodies. The land_ptr == sea_ptr comparison confirms this
at the function pointer level: false, because the two really are different
symbols. Without namespaces, two compute functions with the same signature could not
be defined in the same translation unit; it would be a redefinition error. (The
static/extern linkage measured in M11/K03’s Building Libraries lesson determined
whether a symbol was confined to one file or shared across files; here a namespace
separates two symbols with the same linkage by name — a different mechanism for
the same problem.) Class itself: a qualified name (land::compute) always reaches
the particular symbol in its own namespace, never crossing paths with a same-named
symbol in another namespace.
A using Declaration Does Not Establish a New Symbol, It Opens Access to an Existing One
When using land::compute; is written inside the use_it function, is the resulting
local compute name a new symbol, or an access path opened to land::compute?
// namespace_using.cpp — a using declaration is not a new symbol, it opens an access path to an existing one
#include <iostream>
namespace land {
int compute(int base) {
return base * 2;
}
}
void use_it() {
using land::compute;
int (*local_ptr)(int) = compute;
int (*direct_ptr)(int) = land::compute;
std::cout << std::boolalpha
<< "is compute reached via using the same as land::compute directly "
<< (local_ptr == direct_ptr) << "\n";
}
int main() {
use_it();
return 0;
}
is compute reached via using the same as land::compute directly true
PS17. using land::compute; allows the name compute to be used unqualified
inside use_it, but what it points to does not change — local_ptr (taken
unqualified) and direct_ptr (taken directly via land::compute) carry the same
function pointer: true. A using declaration does not produce a copy, nor does it
establish a new function; it only tells the compiler “let this unqualified name
correspond to that qualified symbol.” Class itself: after using, the unqualified
call reaches exactly the same symbol as the qualified call.
Two Different Access Paths to a Nested Namespace
company::fleet is a nested namespace. vehicle_count is reached by one function
through the fully qualified name, and by another through a namespace alias
(namespace f = company::fleet;). Do both reach the same variable?
// namespace_nested.cpp — a fully qualified name and an alias reach the same variable
#include <iostream>
namespace company::fleet {
int vehicle_count = 12;
}
void* first_access() {
return static_cast<void*>(&company::fleet::vehicle_count);
}
void* second_access() {
namespace f = company::fleet;
return static_cast<void*>(&f::vehicle_count);
}
int main() {
std::cout << std::boolalpha
<< "are the variable reached by fully qualified name and by alias the same "
<< (first_access() == second_access()) << "\n";
return 0;
}
are the variable reached by fully qualified name and by alias the same true
PS18. namespace company::fleet { ... } opens two levels in a single line using
C++17’s nested namespace syntax. first_access reaches vehicle_count by the fully
qualified name; second_access first defines f as an alias of company::fleet,
then reaches it through f::vehicle_count. Comparing the addresses returned by the two
access paths gives true: an alias, just like a using declaration, does not
establish a new variable, it opens a shortened path to the one that already exists.
Class itself: however different the access path looks (full name, alias, using),
all of them reach the same particular symbol.
The Unnamed Namespace: Confining a Symbol to Its Own Unit
Two different translation units each define a function named value inside their own
unnamed (anonymous) namespace. When the two units are compiled and linked separately,
does this name collision produce a link error, or does each unit keep its own
definition?
// namespace_internal_linkage_unit_a.cpp — a symbol inside an unnamed namespace is specific to this unit, not visible from outside
namespace {
int value() { return 111; }
}
int unit_a_value() {
return value();
}
// namespace_internal_linkage_unit_b.cpp — another same-named unnamed namespace symbol, in a different unit, does not collide
namespace {
int value() { return 222; }
}
int unit_b_value() {
return value();
}
// namespace_internal_linkage_main.cpp — the same-named unnamed namespace symbol in two different units compiles and links without colliding
#include <iostream>
int unit_a_value();
int unit_b_value();
int main() {
std::cout << "unit_a_value() result " << unit_a_value() << "\n";
std::cout << "unit_b_value() result " << unit_b_value() << "\n";
return 0;
}
unit_a_value() result 111 unit_b_value() result 222
PS41. Both units call their own value(), and each returns its own value —
111 and 222, with no crossover at all. Every symbol inside an unnamed
namespace is turned by the compiler into a name specific to that translation unit,
invisible from outside; this is why two separate .cpp files can each define a
value() with the same name without the linker seeing it as a collision. This gives
the same result as the static linkage measured in M11/K03 — confining a symbol to
a single file — but the unnamed namespace provides this with the same syntax not only
for functions but for classes and variables as well. Class itself: the name
value inside unit_a_value() reaches only unit_a’s own definition, never crossing
with unit_b’s value.
A using Declaration Opens a Single Symbol, a using Directive Opens the Whole Namespace
PS17’s using land::compute; was a declaration — it made a single symbol
accessible unqualified. using namespace land; is a directive — it opens the
entire namespace. If only one of the same namespace’s two functions (compute,
double_it) is brought in with a declaration, does the other still need a qualified
name; does the directive make both reachable unqualified?
// namespace_declaration_vs_directive.cpp — a using declaration opens a single symbol, a using directive opens the whole namespace
#include <iostream>
namespace land {
int compute(int base) { return base * 2; }
int double_it(int base) { return base * 2; }
}
void declaration_only() {
using land::compute; // only compute becomes accessible unqualified
std::cout << "declaration_only: compute(5) = " << compute(5) << "\n";
std::cout << "declaration_only: land::double_it(5) = " << land::double_it(5) << "\n";
}
void whole_namespace() {
using namespace land; // all of land's symbols become accessible unqualified
std::cout << "whole_namespace: compute(5) = " << compute(5) << "\n";
std::cout << "whole_namespace: double_it(5) = " << double_it(5) << "\n";
}
int main() {
declaration_only();
whole_namespace();
return 0;
}
declaration_only: compute(5) = 10 declaration_only: land::double_it(5) = 10 whole_namespace: compute(5) = 10 whole_namespace: double_it(5) = 10
PS42. Inside declaration_only, compute(5) can be called unqualified, but
double_it must still be qualified as land::double_it(5) — the declaration only
opens the symbol you named, it does not touch the rest of land. Inside
whole_namespace, after using namespace land;, both compute(5) and double_it(5)
can be called unqualified — the directive adds the entire namespace to the lookup
scope. Both reach the same two functions (both return 10); the difference is not in
the result, but in which names become usable unqualified. Class itself: the
declaration and the directive open access to the same symbols with different scope
widths, they do not change the symbols themselves.
Multi-Unit Header/Source Separation: inline Permits Repeating the Same Definition
A function written in a header file and included into more than one .cpp file ends
up repeatedly defined, once per included file — an ordinary function finds this in
violation of the one-definition rule (ODR). Does the inline keyword really legitimize
this repetition?
// namespace_inline_header_unit1.cpp — an inline function, assumed to come from a header file, is defined in this unit
inline int twice(int x) {
return x * 2;
}
int unit1_twice(int x) {
return twice(x);
}
// namespace_inline_header_unit2.cpp — a token-for-token identical definition of the same inline function is repeated in another unit
inline int twice(int x) {
return x * 2;
}
int unit2_twice(int x) {
return twice(x);
}
// namespace_inline_header_main.cpp — the identical inline definition in two different units compiles and links without violating the one-definition rule
#include <iostream>
int unit1_twice(int x);
int unit2_twice(int x);
int main() {
std::cout << "unit1_twice(21) result " << unit1_twice(21) << "\n";
std::cout << "unit2_twice(21) result " << unit2_twice(21) << "\n";
return 0;
}
unit1_twice(21) result 42 unit2_twice(21) result 42
PS43. Two separate translation units define twice independently of each
other, but token-for-token identically — this simulates a header file being
included into two .cpp files. The program compiles and links successfully, and both
units produce the same result: 42. inline opens an exception to the one-
definition rule: a function can be defined in more than one translation unit, as long
as every definition is exactly the same. This is the exact opposite strategy of the
unnamed namespace’s (PS41) solution — the unnamed namespace prevented collision by
hiding the symbol, inline prevents it by sharing the symbol and turning
sameness of the definitions into a contract. If the two definitions differed (say, one
returned x * 2 and the other x * 3), the program would be ill-formed according to
the standard, and the compiler would not be required to diagnose it (no diagnostic
required) — a compiler cannot always catch this, so it was never run here; only the
case that follows the rule, where the identical definition is repeated, was measured.
Class itself: twice corresponds to the same symbol in both units, not multiple
copies but the same contract expressed in two places.
The Contrast Between static and a Shared (extern) Symbol
PS41 provided internal linkage via the unnamed namespace. The static keyword gives
the same effect directly at file scope. Does making a function static really confine
it to its file; conversely, can an ordinary function defined without static or an
unnamed namespace (whose default linkage is extern) really be called from another
unit?
// namespace_linkage_unit1.cpp — a static, internally-linked symbol is specific to this unit, an extern (default) symbol is open to the outside
static int internal_value() {
return 7;
}
int external_value() {
return 700;
}
int unit1_use_internal_value() {
return internal_value();
}
// namespace_linkage_unit2.cpp — another same-named static symbol, in a different unit, exists without colliding
static int internal_value() {
return 9;
}
int unit2_use_internal_value() {
return internal_value();
}
// namespace_linkage_main.cpp — a static symbol is confined to each unit, an extern (default) symbol can be called directly from any unit
#include <iostream>
int unit1_use_internal_value();
int unit2_use_internal_value();
int external_value();
int main() {
std::cout << "unit1_use_internal_value() result " << unit1_use_internal_value() << "\n";
std::cout << "unit2_use_internal_value() result " << unit2_use_internal_value() << "\n";
std::cout << "external_value() called directly from the main unit, result " << external_value() << "\n";
return 0;
}
unit1_use_internal_value() result 7 unit2_use_internal_value() result 9 external_value() called directly from the main unit, result 700
PS44. unit1 and unit2‘s own static internal_value()s give exactly the same
result as PS41’s unnamed namespace example: 7 and 9, with no crossover —
static gives a function at file scope internal linkage, just like the unnamed
namespace. But external_value() is not inside a static or an unnamed namespace at
all; its default linkage is extern, and this is why it can be called directly
from the main unit just by being declared (int external_value();) — 700, reaching
the single object defined in its own unit. All three mechanisms were measured in this
lesson: the unnamed namespace (PS41) and static (here) confine a symbol to its file,
inline (PS43) shares the symbol while requiring the definitions to stay identical,
and ordinary extern linkage gathers the symbol into a single definition accessible
from anywhere. Class itself: all three mechanisms solve the same name-collision
problem by changing the symbol’s visibility scope; the symbol itself always stays
particular.
The Bounding Measurement
The multi-unit examples measured in this lesson simulated including an actual header
file (.h) into multiple .cpp files by manually copying the same definition into
two source units instead — the preprocessor’s own textual copying during #include
was not separately measured in this lesson. Argument-dependent lookup (ADL, the
mechanism that finds an operator like std::cout << without a qualifying name) is
outside this lesson’s scope; here every call was made either with a qualified name or
with an explicit using. The case where the one-definition rule is violated — two
different, non-inline definitions in two units colliding under the same externally
visible name — results in a link error or undefined behavior and so was never run;
only the cases that follow the rule were measured. A namespace itself (independent
of its functions) being extended across multiple files with multiple namespace land { ... } blocks was also not shown; here namespaces always stayed defined within a single
file, only the linkage kind of the symbols inside them was tested across multiple
units.
Summary
- Same-named functions in two different namespaces are independent symbols; a
qualified name (
land::compute) always reaches its own definition. - A function pointer comparison confirms this:
land::computeandsea::computeare not the same function. - A
usingdeclaration does not establish a new symbol; it binds the unqualified name to an existing qualified symbol — class itself. - A namespace alias (
namespace f = company::fleet;) works on the same principle: it does not establish a new variable, it opens a shortened access path to the existing one. - The fully qualified name,
using, and the alias — all three reach the same particular symbol; only the spelling differs.
Next Step
static_cast<void*>(...) was used frequently throughout this lesson, but what
static_cast guarantees, or how a class can define its own conversion rules, was
never opened up — it was only used as a tool for comparing addresses. The course’s
closing lesson, Type Conversion Operators, takes this up directly: it measures how a
class can offer an implicit or explicit conversion to a type other than its own, and
whether the value produced when such a conversion is invoked works with the original
object or with a new object derived from it.
To keep your progress and take notes, Log in
My notes
Log in to take notes.