Namespaces, Modules, and the Preprocessor
|
This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com. This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Namespaces
A namespace groups related names and avoids collisions between libraries:
namespace geometry {
struct Point { double x, y; };
double distance(Point a, Point b);
}
geometry::Point p{1.0, 2.0}; // fully qualified
Unnamed and Inline Namespaces
An unnamed namespace gives its contents internal linkage (see Program Structure and Compilation); an inline namespace (C++11) makes its contents visible as if they were in the enclosing namespace, used mainly for versioning a library’s ABI:
namespace mylib {
inline namespace v2 { // "inline": mylib::Widget resolves to mylib::v2::Widget transparently
struct Widget { int id; };
}
namespace v1 { // still reachable explicitly for old callers: mylib::v1::Widget
struct Widget { int id; };
}
}
mylib::Widget w{1}; // resolves to mylib::v2::Widget -- the current version
mylib::v1::Widget legacy{1}; // still available, explicitly
using-Declarations and using-Directives
#include <string>
using std::string; // using-declaration: brings ONE name into scope
// using namespace std; // using-directive: brings EVERY name in std:: into scope -- avoid at
// namespace/global scope in headers; it leaks into every includer
void demo() {
using namespace std::literals; // fine in a narrow local scope, like a function body
auto s = "hi"s;
(void)s;
}
string greeting = "hello";
Argument-Dependent Lookup (ADL)
ADL ("Koenig lookup") finds a free function based on the namespace of its arguments, even without
qualification or a using-declaration — it’s why std::swap(a, b) usually isn’t needed and plain swap(a,
b) (found via ADL if a/b’s type defines its own `swap in its own namespace) works, and it’s the entire
mechanism behind range-for’s unqualified `begin(x)/end(x) calls:
namespace geometry {
struct Point { double x, y; };
void describe(Point p) { /* ... */ } // found via ADL below, with no "geometry::" prefix needed
}
void demo() {
geometry::Point p{1, 2};
describe(p); // ADL finds geometry::describe because p's type lives in namespace geometry
}
The Preprocessor
#define MAX_SIZE 100 // object-like macro
#define SQUARE(x) ((x) * (x)) // function-like macro -- parenthesize every use of x, and the whole result
#if defined(_WIN32)
#define PLATFORM "Windows"
#elif defined(__linux__)
#define PLATFORM "Linux"
#else
#define PLATFORM "Unknown"
#endif
#define STRINGIFY(x) #x // stringification: STRINGIFY(hello) -> "hello"
#define CONCAT(a, b) a##b // concatenation: CONCAT(foo, bar) -> foobar
#define LOG(fmt, ...) printf(fmt __VA_OPT__(,) __VA_ARGS__) // C++20: __VA_OPT__(,) inserts the comma ONLY
// if variadic arguments were actually passed,
// avoiding LOG("no args") producing "fmt,"
Prefer constexpr/inline/templates over macros wherever possible — macros have no scope, no type checking,
and are invisible to the debugger; they remain necessary mainly for conditional compilation and
stringification/concatenation, which no other C++ feature replaces.
Modules and Module Partitions
Module basics were introduced in Program Structure and Compilation. A module can be split into partitions — internal pieces assembled into one logical module, useful for organizing a large module’s implementation across multiple files:
// math-basics.cppm -- a module partition (note the colon)
export module math:basics;
export int add(int a, int b) { return a + b; }
// math.cppm -- the primary module interface, importing and re-exporting its partitions
export module math;
export import :basics;
export int addTwice(int a, int b) { return add(a, b) * 2; }
Feature-Test Macros and <version>
Feature-test macros let code check, at preprocessing time, whether a given language/library feature is available — essential for code that must build across multiple compiler versions:
#include <version>
#if __cpp_lib_expected >= 202202L
#include <expected>
// use std::expected
#else
// fall back to a manual error-code/variant-based approach
#endif
#if defined(__cpp_concepts) && __cpp_concepts >= 201907L
// concepts are available
#endif
<version> centralizes every standard-library feature-test macro in one header, rather than requiring the
actual feature header to be included just to test for its presence.
See Also
-
C: Preprocessor and Macros — the same preprocessor, carrying far more of the load in the absence of namespaces, templates and modules.