skip to content
Mehdi Mehdikhani
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 version
MyLib::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 change
namespace 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:

  1. 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 default
Graphics::Renderer renderer; // v2::Renderer
  1. 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 version
auto home = FileSystem::get_home_directory();
  1. 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 match
Math::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 default
Logger::log(Logger::Level::Info, "System started");
// Old code still works
Logger::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.