Namespaces & Modules
As programs grow, names start to clash. Your billing code has a function called print_total, and so does a library you downloaded. Two teams both create a class called Student. Namespaces solve this by giving names a surname: school::Student and college::Student can live side by side. You have used one namespace since your first program: std, the home of the standard library.
The second half of this lesson introduces modules, the C++20 replacement for header files, and C++23's import std;.
Creating and using namespaces
A namespace is a named region that holds declarations. Outside it, you reach its names with ::.
#include <print>
#include <string>
namespace school {
int students = 1200;
void announce(const std::string& message) {
std::println("[School] {}", message);
}
}
namespace college {
int students = 4500;
void announce(const std::string& message) {
std::println("[College] {}", message);
}
}
int main() {
school::announce("Holiday on Friday");
college::announce("Exams start on Monday");
std::println("Total students: {}", school::students + college::students);
}[School] Holiday on Friday [College] Exams start on Monday Total students: 5700
Both namespaces define students and announce, and there is no conflict because their full names differ. A namespace can be opened again in another place, or another file, to add more names.
Nested namespaces, aliases and using
Large projects nest namespaces, for example company::project::module. C++17 lets you write a nested namespace in one line. Long names can be shortened with a namespace alias, and a using declaration brings one specific name into the current scope.
#include <print>
namespace shop::billing { // C++17 nested namespace
constexpr double gst_rate = 0.18;
double with_gst(double amount) { return amount * (1 + gst_rate); }
}
namespace bill = shop::billing; // alias
int main() {
std::println("{:.2f}", shop::billing::with_gst(100));
std::println("{:.2f}", bill::with_gst(250));
using shop::billing::with_gst; // just this one name
std::println("{:.2f}", with_gst(50));
}118.00 295.00 59.00
There is also the using directive, using namespace shop::billing;, which brings in every name from the namespace. It is acceptable inside a function or a small .cpp file, but never put using namespace in a header file: every file that includes the header would receive all those names, and clashes would appear far from their cause. The famous using namespace std; has the same problem, which is why this course always writes std::.
One exception is widely accepted: the standard literal namespaces, which only contain suffixes.
#include <print>
#include <string>
#include <string_view>
int main() {
using namespace std::literals; // enables the s and sv suffixes
auto city = "Kochi"s; // std::string, not a C string
auto state = "Kerala"sv; // std::string_view
std::println("{}, {} ({} letters)", city, state, city.size() + state.size());
}Kochi, Kerala (11 letters)
Internal names: anonymous namespaces
A namespace without a name makes its contents visible only inside the current .cpp file. Use it for helper functions and variables that other files must not see or clash with:
namespace {
int calls = 0; // private to this file
bool is_valid_pin(int pin) { return pin >= 100000 && pin <= 999999; }
}This replaces the old C habit of marking such functions static.
Modules: the modern alternative to headers
#include works by copying the whole text of a header into your file. A program that includes <vector> and <string> in 200 files makes the compiler read and process those headers 200 times. Macros defined in one header leak into everything included after it, and the order of includes can change the meaning of code.
C++20 modules fix this. A module is compiled once into an efficient binary form, then imported by any file that needs it. Only names marked export are visible to importers, and macros never leak out. C++23 adds the standard library as a module: import std; gives you the whole library in one line.
// billing.cppm: a module interface unit
export module billing;
import std;
export namespace billing {
double with_gst(double amount) {
return amount * 1.18;
}
std::string receipt(const std::string& item, double amount) {
return std::format("{}: Rs. {:.2f}", item, with_gst(amount));
}
}
double round_paise(double x) { return x; } // not exported: invisible outside// main.cpp
import std;
import billing;
int main() {
std::println("{}", billing::receipt("Calculator", 500));
}Calculator: Rs. 590.00
export module billing; names the module. export in front of the namespace makes everything inside it available to importers; round_paise stays private to the module. main.cpp needs no #include at all.
#include headers | Modules | |
|---|---|---|
| How it works | Text copied into each file | Compiled once, then imported |
| Build speed in large projects | Slower | Faster |
| Macros | Leak into including files | Do not leak |
| What is visible | Everything in the header | Only exported names |
| Tool support | Universal | Recent compilers and build systems |
Common mistakes
using namespacein a header. It forces the names on every file that includes it. Use full names in headers.- Defining the same name twice in one namespace. Namespaces prevent clashes between namespaces, not inside one.
- Forgetting
exportin a module. Names that are not exported cannot be used by importers, and the error says the name was not declared. - Mixing
#include <...>andimport std;carelessly. Some compilers handle the combination poorly; in a module-based project, prefer importing.
Key points
- Namespaces group names and prevent clashes; reach their contents with
name::member. - Nested namespaces can be written as
namespace a::b; aliases (namespace x = a::b;) shorten long names. - Prefer using declarations for single names; never put
using namespacein a header. - An anonymous namespace makes names private to one
.cppfile. - C++20 modules (
export module,import) are compiled once, export only chosen names and do not leak macros; C++23 addsimport std;.
Exercise
Create a namespace railway::fares containing a constexpr int base_fare = 30; and a function int fare(int km) that returns the base fare plus 2 rupees per km. Create an alias namespace rf = railway::fares; and, in main, print the fare for 45 km using the full name and for 120 km using the alias. Then use a using declaration to call fare(10) without any prefix.
Show solution
#include <print>
namespace railway::fares {
constexpr int base_fare = 30;
int fare(int km) { return base_fare + 2 * km; }
}
namespace rf = railway::fares;
int main() {
std::println("45 km: Rs. {}", railway::fares::fare(45));
std::println("120 km: Rs. {}", rf::fare(120));
using railway::fares::fare;
std::println("10 km: Rs. {}", fare(10));
}45 km: Rs. 120 120 km: Rs. 270 10 km: Rs. 50
All three calls reach the same function; the namespace alias and the using declaration only change how much you type.
Quick Quiz
1. Why should using namespace std; never appear in a header file?
A using directive in a header spreads to every including file, far from where problems appear.
2. What does an anonymous namespace do?
Names in an unnamed namespace have internal linkage, like static functions in C.
3. In a module, which names can importing files use?
Modules hide everything that is not explicitly exported.
4. What does import std; give you?
C++23 added the std module, which provides the entire standard library through one import.
Learn C++ with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
