Inline Namespaces in C++
/ 4 min read
Table of Contents
Inline namespaces are a C++11 feature I didn’t appreciate until I needed to version APIs. They let you evolve code while maintaining backward compatibility.
What inline namespace Actually Does
An inline namespace makes its contents available in the parent namespace:
namespace MyLib { inline namespace v2 { void func() { std::cout << "Version 2" << std::endl; } }
namespace v1 { void func() { std::cout << "Version 1" << std::endl; } }}
MyLib::func(); // Calls v2::func() - the inline versionMyLib::v1::func(); // Explicitly calls v1::func()MyLib::v2::func(); // Explicitly calls v2::func()The inline namespace is the “default” version when you don’t specify which one.
Why This Is Useful
Before inline namespaces, API versioning was painful:
// The old way - breaking changenamespace MyLib { void old_function(); // Remove this = break users void new_function(); // Add this = users must change code}With inline namespaces, you can evolve APIs smoothly:
namespace MyLib { inline namespace v3 { void improved_function(); // New default }
namespace v2 { void function(); // Still available }
namespace v1 { void legacy_function(); // Still available }}When I Actually Use Inline Namespaces
Most of the time, I use inline namespaces for:
- API versioning: The most common use case:
namespace Graphics { inline namespace v2 { class Renderer { public: void render_advanced(const Scene& scene); }; }
namespace v1 { class Renderer { public: void render_basic(const Scene& scene); }; }}
// Users get v2 by defaultGraphics::Renderer renderer; // v2::Renderer- Platform-specific implementations: Different versions for different platforms:
namespace FileSystem {#ifdef WINDOWS inline namespace win32 { std::string get_home_directory(); }#else inline namespace posix { std::string get_home_directory(); }#endif}
// Always call the right versionauto home = FileSystem::get_home_directory();- Feature toggles: Enable/disable features at compile time:
namespace Database {#ifdef ENABLE_ASYNC inline namespace async_version { class Connection { public: std::future<Result> query(const std::string& sql); }; }#else inline namespace sync_version { class Connection { public: Result query(const std::string& sql); }; }#endif}Standard Library Usage
The standard library uses inline namespaces extensively:
namespace std { inline namespace __cxx11 { // C++11 ABI version of string, list, etc. }
namespace __cxx98 { // Pre-C++11 ABI versions }}This is how your standard library can provide both old and new versions of containers without breaking ABI compatibility.
The Gotchas
Inline namespaces can create naming conflicts:
namespace Library { inline namespace v2 { void process(); }
namespace v1 { void process(); }
void process(); // Error! Ambiguous with v2::process()}Also, overload resolution works across inline namespaces:
namespace Math { inline namespace v2 { void calculate(double x); }
namespace v1 { void calculate(int x); }}
Math::calculate(42); // Calls v1::calculate(int) - better matchMath::calculate(3.14); // Calls v2::calculate(double)Real World Example
Here’s how I use it for a logging library:
namespace Logger { inline namespace v3 { enum class Level { Debug, Info, Warning, Error };
void log(Level level, const std::string& message); void log(Level level, const std::string& format, auto... args); // C++20 formatted }
namespace v2 { enum LogLevel { DEBUG, INFO, WARNING, ERROR };
void log(LogLevel level, const std::string& message); }
namespace v1 { void debug(const std::string& msg); void info(const std::string& msg); void warning(const std::string& msg); void error(const std::string& msg); }}
// New code uses v3 by defaultLogger::log(Logger::Level::Info, "System started");
// Old code still worksLogger::v2::log(Logger::v2::INFO, "Legacy message");Logger::v1::info("Really old code");The Pattern I Follow
Start with inline namespaces from the beginning if you’re building a library:
namespace MyLibrary { inline namespace v1 { // Your initial API }}When you need to make breaking changes, create v2 and make it inline:
namespace MyLibrary { inline namespace v2 { // New improved API }
namespace v1 { // Legacy API still available }}Inline namespaces are well suited for maintaining backward compatibility while allowing APIs to evolve. They’re especially useful for library authors.