Skip to content
academia.sh

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::compute and sea::compute are not the same function.
  • A using declaration 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.

Start typing to search.

↑↓ ↵ Esc navigate · open · close