C++ / NAMESPACES, HEADERS, AND BUILDS
Namespaces and resolving name collisions
Split code into namespaces, shorten names with aliases and using-declarations, and fix ambiguous unqualified calls without editing either library.
What you will learn
- Qualify calls fully, or shorten a long path with a namespace alias
- Tell a using-declaration (one name) apart from a using-directive (all names)
- Keep using-directives out of headers and inside the smallest scope that needs them
- Reopen an existing namespace to add names; a misspelling silently makes a new one
Understanding Namespaces and resolving name collisions
A namespace is a named scope whose name becomes part of the full name of everything declared inside it. metric::to_meters and imperial::to_meters are two different names, so two libraries can each define a conversion function without either author knowing about the other. The useful mental model is a filing system for names, not a module system and not an access control mechanism: nothing is hidden, and the code inside compiles exactly as it would outside.
C++ gives two ways to write less at the call site, and they are not the same tool. A using-declaration, using metric::to_meters;, declares that one name in the current scope, where it can be hidden by a later declaration and can participate in overloading with other declarations there. A using-directive, using namespace metric;, copies nothing: it makes every name in metric a candidate for unqualified lookup as if it had been declared in the nearest namespace that encloses both the directive and metric. Because a directive only adds candidates, a clash is not an error where the directive appears; it is an error at the call that ends up with two equally good matches.
Unqualified lookup walks outward from the current scope and stops at the first scope that declares the name at all, without first checking whether the arguments fit. That is why a function added to an inner namespace hides a same-named function in the enclosing namespace and gives you a conversion error instead of quietly falling back to the outer one. So resolving collisions is really about controlling how much of the name you write: qualify fully, shorten a long path with namespace alias = a::b::c;, reach a global name with ::name, and use an inline namespace when a new default should be reachable as lib::name while lib::v1::name keeps working.
<iostream>
namespace metric {
double to_meters(double value) { return value; }
const char* name() { return "metric"; }
}
namespace imperial {
double to_meters(double feet) { return feet * 0.3048; }
const char* name() { return "imperial"; }
}
namespace app::config { // C++17 nested definition
int precision = 3;
}
int main() {
std::cout << metric::name() << ": " << metric::to_meters(100.0) << " m\n";
std::cout << imperial::name() << ": " << imperial::to_meters(100.0) << " m\n";
{
using namespace imperial; // directive: candidates only inside this block
std::cout << "block, unqualified: " << to_meters(1.0) << " m\n";
}
using metric::to_meters; // declaration: one name, this scope
std::cout << "after using-declaration: " << to_meters(1.0) << " m\n";
namespace cfg = app::config;
std::cout << "precision: " << cfg::precision << '\n';
}
The namespace is part of an entity's name, so collisions are resolved by choosing how much of that name each call site spells out, not by hiding code.
Worked examples
Nested namespaces and an alias
Shows that code inside a nested namespace sees the enclosing namespace's names unqualified, while callers outside need the full path or an alias.
<iostream>
namespace net {
int timeout_ms = 250;
namespace http {
int retries = 2;
void report() {
std::cout << "http: retries=" << retries
<< " timeout=" << timeout_ms << "ms\n";
}
}
namespace tcp {
int retries = 5;
void report() {
std::cout << "tcp: retries=" << retries
<< " timeout=" << timeout_ms << "ms\n";
}
}
}
int main() {
namespace h = net::http;
h::report();
net::tcp::report();
std::cout << "both retries: " << net::http::retries
<< " and " << net::tcp::retries << '\n';
}
Example explained
Line 1Inside net::http, timeout_ms needs no qualification: lookup fails in http, then finds it in the enclosing net.
Line 2retries resolves to net::http::retries in one report and net::tcp::retries in the other, because lookup stops at the innermost scope that declares the name.
Line 3namespace h = net::http; is a block-scope alias, so h::report() and net::http::report() name the same function.
Line 4From main, nothing is visible unqualified, which is exactly why the two retries variables never collide.
Inline namespace for versioning
Demonstrates how an inline namespace picks a default version without breaking callers that already spell out the old version.
<iostream>
namespace lib {
inline namespace v2 {
const char* version() { return "v2"; }
int area(int w, int h) { return w * h; }
}
namespace v1 {
const char* version() { return "v1"; }
int area(int side) { return side * side; }
}
}
int main() {
std::cout << lib::version() << " area=" << lib::area(3, 4) << '\n';
std::cout << lib::v1::version() << " area=" << lib::v1::area(3) << '\n';
std::cout << lib::v2::version() << '\n';
}
Example explained
Line 1inline makes every member of v2 also a member of lib, so lib::area(3, 4) resolves with no version in the name.
Line 2lib::v1::area(3) still compiles, because inline adds a second path to a name instead of removing the old one.
Line 3The one-argument and two-argument area never clash: only the inline version is reachable as lib::area.
Line 4Moving inline from v2 to v1 would change what lib::area means for every caller, with no edits at the call sites.
Reaching the global namespace with ::
Shows a namespace member shadowing a global of the same name, and how :: names the global one explicitly.
<iostream>
int value = 10;
namespace tools {
int value = 20;
int show() { return value; } // tools::value
int show_global() { return ::value; } // the global one
}
int main() {
std::cout << ::value << ' ' << tools::value << '\n';
std::cout << tools::show() << ' ' << tools::show_global() << '\n';
}
Example explained
Line 1Inside tools, plain value finds tools::value first, so show() returns 20.
Line 2The leading :: in ::value forces lookup to start at the global namespace, giving 10.
Line 3Writing using namespace tools; in main would make an unqualified value ambiguous, since ::value and tools::value would both be candidates in the global namespace.
Line 4Both variables exist at once; nothing was overwritten, only named differently.
Important notes
namespace app::config { } requires C++17; before that you must write nested namespace blocks, and an inline namespace needs C++11.
A using-directive inside a function body stops at the closing brace, which is why the block in the main example is safe while the same line at file scope would affect everything below it.
Common mistakes
Writing using namespace std; in a header: every translation unit that includes it inherits thousands of names, and a user's own size, data, distance or count later fails with an ambiguous-call error in a file that never asked for the directive.
Adding a function to an inner namespace that shares a name with one in the enclosing namespace: lookup stops at the inner declaration, so calling it with the outer overload's argument types gives a no-matching-function or conversion error instead of falling back outward.
Misspelling a namespace when reopening it, as in namespace app { namespace confg { ... } }: this compiles fine because it defines a brand new namespace, and the function declared in app::config is simply never defined, which surfaces as an unresolved symbol at link time.
Try it yourself
Change, predict, then run
Define namespaces celsius and fahrenheit, each with to_kelvin(double) and label(), and print both results for 100.0 using fully qualified calls. Then add an inner block with using namespace fahrenheit; that calls to_kelvin(212.0) unqualified, and confirm the same unqualified call after the block no longer compiles.
Open the C++ workspaceCheck your understanding
A file declares void f(int) in namespace a and void f(int) in namespace b, then has both using namespace a; and using namespace b; at global scope. What does the compiler do?
- The second using-directive is rejected, because a name f is already visible at global scope.
- The file compiles, and an ambiguity is reported only where an unqualified f(1) is actually called.
- The later directive wins, so an unqualified f(1) calls b::f.
- Both declarations are injected into the global namespace, producing a duplicate-symbol error at link time.
Show answer
A using-directive only adds candidates to unqualified lookup, and lookup happens at the point of use, so nothing is wrong until a call gives overload resolution two equally good matches. The 'later directive wins' option is tempting because directives look like imports in other languages, but C++ has no ordering rule between them and no name is copied or replaced; qualifying the call as a::f(1) is what resolves it.