5.2.4 · D4C++ Programming

Exercises — Namespaces — avoiding name collisions

2,626 words12 min readBack to topic

Before we start, one picture to keep in your head for the whole page — the "prefix box" model of a name.

Figure — Namespaces — avoiding name collisions

Every name you write is really a full name = the box labels stacked with ::, then the identifier at the end. Two names collide only when their full names match exactly.


Level 1 — Recognition

Goal: recognise the syntax and what each piece means. No tracing yet.

Recall Solution

:: is the scope-resolution operator. It says "look for the name on the right inside the scope named on the left" — here, find cout inside the std namespace, so the candidate set becomes exactly {std::cout}.

Recall Solution
  • (a) = using-declaration — brings in exactly one name, cout.
  • (b) = Qualified — the full name is spelled out, always unambiguous.
  • (c) = using-directive — dumps all of std into the current scope (the risky one). Mnemonic from the parent: Q-D-D = Qualify, Declare one, Dump all.
Recall Solution

A leading :: with nothing before it means "start lookup at the very top — the global (unnamed) namespace." So ::swap forces the global swap, ignoring any local using that pulled in a different swap.


Level 2 — Application

Goal: predict what a small program prints or whether it compiles.

Recall Solution
  • Geometry::area(2.0) .
  • Statistics::area(3.0, 4.0) . Same short name area, different full names (Geometry::area vs Statistics::area) → no clash, and the argument list also differs so even overload resolution is happy.
Recall Solution
  • Unqualified parse in parse + 1 resolves to the global variable int parse = 5, so a = 5 + 1 = 6.
  • JsonLib::parse("{}") is fully qualified, so it calls the library function, returning 42, so b = 42. The full names ::parse (variable) and JsonLib::parse (function) differ → both legal at once.
Recall Solution

Active is an alias for FastImpl, so Active::sqrt_(16) calls FastImpl::sqrt_(16) = 16/2 = 8, giving r = 8. Switching the alias to ExactImpl makes it ExactImpl::sqrt_(16) = 16/4 + 1 = 4 + 1 = 5. One line flips every Active:: call — no collisions because only the synonym changed, not any name.


Level 3 — Analysis

Goal: trace name lookup step by step and diagnose errors.

The lookup rule, as a diagram — memorise the order:

Figure — Namespaces — avoiding name collisions
Recall Solution

Both using-directives inject their f into the current scope with equal rank. When the compiler does lookup on unqualified f, it finds two equally good candidates {A::f, B::f}ambiguity error (it refuses to guess). Fix: qualify → A::f();. This forces the candidate set to exactly {A::f}, one match, done.

Recall Solution

Two levels are visible with equal rank inside g (the global ::level and the using-directive's Tools::level), so unqualified level is ambiguous — it will not compile. To pick one explicitly:

  • global one → ::level (value 99),
  • Tools one → Tools::level (value 1). Leading :: = "start at the top"; the Tools:: prefix pins the other.
Recall Solution

z = 1. A using-declaration (using Tools::level;) acts like a local declaration of that name inside h. A locally declared name hides the global ::level rather than competing at equal rank — the nearest scope wins outright. So unqualified level is Tools::level = 1, unambiguously. Contrast with L3.2's using-directive, which does not declare locally, so both names stayed at equal rank and clashed.


Level 4 — Synthesis

Goal: design namespace structure to solve a real organisation problem.

Recall Solution
// net.h
#ifndef NET_H          // include guard (see Header Files topic)
#define NET_H
namespace Net {
    void send();       // full name: Net::send  — declaration only
}
#endif
// net.cpp
#include "net.h"
void Net::send() { /* real code, defined ONCE */ }
// user.cpp
#include "net.h"
void send() { /* user's own */ }   // full name ::send — no clash!
Net::send();                       // explicitly your library's

Why it works: the header puts send in Net (full name Net::send) and contains no using namespace, so it adds nothing to the user's unqualified scope. The user's ::send and your Net::send have different full names → both coexist. Also note: the header only declares; the single definition lives in net.cpp, honouring the One Definition Rule (ODR).

Recall Solution

Technique A — alias (recommended, local & explicit):

namespace CPN = Company::Project::Net;
int a = CPN::port;   // short, still uniquely CPN == Company::Project::Net

Technique B — targeted using-declaration:

using Company::Project::Net::port;
int a = port;        // brings in ONLY 'port'

Both keep uniqueness. Avoid using namespace Company::Project::Net; if the file has other port-like names, since a directive risks ambiguity (L3 lessons).


Level 5 — Mastery

Goal: edge cases — linkage, ODR, and unnamed namespaces.

Figure — Namespaces — avoiding name collisions
Recall Solution

No — linker error. A namespace is open: it spans files, so App::counter in a.cpp and App::counter in b.cpp are two definitions of one entity → an One Definition Rule (ODR) violation. The linker sees App::counter defined twice. Correct pattern: declare once in a header with extern, define once in a single .cpp:

// app.h
namespace App { extern int counter; }   // declaration (no storage)
// app.cpp
namespace App { int counter = 0; }      // the ONE definition

extern says "this name exists somewhere with external linkage — don't allocate it here" (see Linkage — internal vs external).

Recall Solution

No clash. An unnamed namespace gives its members internal linkage — each is visible only within its own translation unit (.cpp). So helper/tick in file1.cpp and in file2.cpp are distinct entities the linker never compares. This is the modern replacement for file-scope static. (See Scope and Storage Duration and Linkage — internal vs external.)

Recall Solution

The using-directive made My::swap visible, so unqualified swap(a,b) risks the buggy one. Force the library's:

std::swap(a, b);   // full name pins The Standard Library version

After it, a = 7 and b = 3 (values exchanged). Qualifying with std:: shrinks the candidate set to exactly {std::swap}, bypassing My::swap entirely. (See The Standard Library (std).)

Recall Solution

. Each v is pinned by its prefix to a different full name, so there is no ambiguity even though the identifier v repeats.


80/20 recap

Recall The vital few from these exercises

Prefix box makes full names ::: full name = box labels joined by :: then the identifier; collide only if full names match. using-declaration vs using-directive ::: declaration hides competitors (nearest wins); directive adds names at equal rank (can ambiguate). Leading :: means ::: start lookup at the global/top scope. Two definitions of Ns::x in two files ::: ODR violation — declare once with extern, define once. Unnamed namespace gives ::: internal linkage — file-local, no cross-file clash, replaces file-scope static. Force the std version ::: qualify with std::, e.g. std::swap(a,b).


Connections

  • 5.2.04 Namespaces — avoiding name collisions (Hinglish)
  • Scope and Storage Duration
  • One Definition Rule (ODR)
  • Header Files and Include Guards
  • Classes and Encapsulation
  • Linkage — internal vs external
  • The Standard Library (std)