C++ inline Namespace: Versioning, API Evolution, and ADL
Key takeaways
Members of an inline namespace behave as if declared in the enclosing namespace for lookup, template specialization and ADL, but their mangled symbol names still contain the inner namespace. That combination is what makes inline namespaces useful for library versioning: users write mylib::Widget, and the linker sees mylib::v2::Widget.
What an inline namespace does
Added in C++11, an inline namespace is a nested namespace whose members are also visible as members of the enclosing namespace:
#include <iostream>
namespace mylib {
inline namespace v2 {
void func() { std::cout << "v2\n"; }
}
}
int main() {
mylib::func(); // finds mylib::v2::func
mylib::v2::func(); // also fine
}
If that were all, it would just be a shorter way of writing namespace v2 { ... } using namespace v2;. It is not. An inline namespace is treated as part of its parent in three places where a using-directive is not, and it deliberately does not disappear in a fourth:
- Qualified and unqualified lookup:
mylib::funcfindsmylib::v2::func. - Template specialization: you can specialize
mylib::v2::Hash<T>from insidenamespace mylibas if the template were declared there. - Argument-dependent lookup: for a type in
mylib::v2, the namespacemylibis also searched. - Linkage names: the mangled symbol still contains
v2.
The last point is the reason the feature exists. The source code names mylib::func; the object file contains a symbol for mylib::v2::func. That lets a library change the binary layout of its types in a new version without users changing a line of source, while making sure old and new binaries cannot be accidentally linked together.
Versioning an API
#include <iostream>
namespace mylib {
namespace v1 {
void process(int x) { std::cout << "v1: " << x << '\n'; }
}
inline namespace v2 {
void process(int x, int y = 0) { std::cout << "v2: " << x << ", " << y << '\n'; }
}
}
int main() {
mylib::process(10); // v2: 10, 0
mylib::v1::process(10); // v1: 10
}
Code written against the plain mylib:: names moves to v2 automatically on the next build. Code that must keep the old behavior spells mylib::v1:: explicitly. When v3 arrives, the library removes inline from v2 and adds it to v3. It is worth marking the old versions [[deprecated("use mylib::process")]] so those explicit uses show up in compiler output.
Why the version ends up in the symbol
Compile the example above and look at the symbols with nm:
0000000000000000 T _ZN5mylib2v17processEi
000000000000003d T _ZN5mylib2v27processEii
Demangled, those are mylib::v1::process(int) and mylib::v2::process(int, int). The 2v2 component is in the name even though no call site ever wrote v2. Now suppose v2 also changed the layout of a Widget class by adding a member. An application compiled against the v2 headers but linked with a v1 build of the library does not silently pass a v2-sized Widget into v1 code, which would corrupt memory at run time. It fails at link time instead, because it asks for mylib::v2::... symbols that the old library does not contain.
The best-known real example is libstdc++‘s dual ABI. When GCC 5 changed std::string and std::list to meet C++11 requirements, the new versions were placed in the inline namespace std::__cxx11. That is why a mismatched build produces errors such as undefined reference to foo(std::__cxx11::basic_string<char, ...> const&): one side was compiled with the new ABI, the other with _GLIBCXX_USE_CXX11_ABI=0. It is also why a crash log shows types like std::__cxx11::basic_string<char>. libc++ does the same with std::__1. When you see one of these inner names in a linker error, the message is telling you that two parts of the program were built against different versions of a header.
The case I have run into most often with this is a library whose header selects the inline namespace with a macro, for example #if MYLIB_USE_V2, and two parts of the build define that macro differently. With a plain namespace the program would link and misbehave; with the inline namespace it fails to link, and the undefined symbol names exactly which version each side expected. Annoying at the time, but far easier to diagnose than memory corruption.
Specializing templates from the parent namespace
Users commonly specialize a library’s templates for their own types. With an inline namespace, they can do that without knowing which version is current:
namespace mylib {
inline namespace v2 {
template <class T> struct Hash { static const char* name() { return "generic"; } };
}
}
struct Point {};
namespace mylib {
template <> struct Hash<Point> { static const char* name() { return "Point"; } }; // OK
}
Try the same with a plain nested namespace plus using namespace v2; and GCC rejects it:
error: explicit specialization of 'template<class T> struct lib::v2::Hash' outside its namespace must use a nested-name-specifier [-fpermissive]
With the using-directive, the user has to write mylib::v2::Hash<Point>, which hard-codes the version into user code and breaks as soon as the library moves to v3. This was one of the main motivations for adding inline namespaces to the language.
ADL and the enclosing namespace
namespace mylib {
inline namespace v2 {
struct Data {};
}
void describe(Data) {} // declared in the parent, not in v2
}
int main() {
mylib::Data d;
describe(d); // found by ADL
}
For argument-dependent lookup, the associated namespaces of mylib::v2::Data include mylib itself, so free functions and operators declared at the mylib level are found. This is what you want most of the time, since library authors often keep helper overloads in the outer namespace. The flip side is that an overload set can grow when you add an inline namespace: a call that used to find only one describe may now see candidates from both levels. When adding a new version, check that swap, operator<<, operator== and hash functions still resolve to the overloads you expect.
Reopening and declaring
An inline namespace has to be marked inline the first time it is defined. Later reopenings may omit the keyword and remain inline. Getting that backwards is an error:
namespace lib { namespace v3 { void g(); } }
namespace lib { inline namespace v3 { void h(); } }
error: inline namespace must be specified at initial definition
This tends to appear when two headers both open the same versioned namespace and only one of them was updated. The safest arrangement is a single small header that opens namespace lib { inline namespace v3 {} } and that every other header includes first.
C++20 added a compact spelling for nested namespaces that include an inline one:
namespace mylib::inline v2 {
int answer() { return 42; }
}
// mylib::answer() works
Pitfalls
Two inline siblings with the same names
It is legal to have more than one inline namespace under the same parent, but if both declare the same name, any use through the parent becomes ambiguous:
namespace lib {
inline namespace v1 { void func() {} }
inline namespace v2 { void func() {} }
}
int main() { lib::func(); }
error: call of overloaded 'func()' is ambiguous
note: candidate: 'void lib::v2::func()'
note: candidate: 'void lib::v1::func()'
Note that the declarations themselves compile; the error only appears at the call site, possibly in a user’s code. For versioning, keep exactly one version inline at any time.
Treating it as a way to hide things
inline namespace detail { ... } is a common mistake. It makes the implementation details more visible, not less, because they are now reachable as lib::helper. Implementation details belong in a regular namespace detail.
Expecting it to provide ABI compatibility by itself
An inline namespace makes ABI breaks detectable at link time. It does not make old and new binaries compatible. If an application and a plugin must exchange a mylib::Widget across a binary boundary, both must use the same version, or the interface must use types whose layout does not change (see PIMPL). And because the version name is in every symbol, bumping it forces everything that uses those types to be recompiled.
The standard library’s inline namespaces
You use inline namespaces whenever you pull in user-defined literal operators:
#include <chrono>
#include <string>
using namespace std::literals; // all standard literals
auto s = "hello"s; // std::string
auto t = 5s; // std::chrono::seconds
using namespace std::string_literals; // only the string ones
std::literals and std::literals::string_literals are both inline namespaces. You can opt in narrowly with a using for just the group you want, or broadly with std::literals, and code that fully qualifies std::operator""s still works because the operator is also a member of std.
Inline namespace or nested namespace with using
| Question | Plain nested namespace + using | Inline namespace |
|---|---|---|
parent::name finds inner members | Yes | Yes |
Specialize inner templates from parent | No | Yes |
ADL searches parent for inner types | No | Yes |
| Inner name in mangled symbol | Yes | Yes |
| Typical use | Convenience | Versioning, opt-in groups |
There is no run-time cost; everything happens during name lookup and symbol naming.
Related Articles
- C++ Namespaces: using-Declarations vs Directives, Anonymous Namespaces and ADL
- C++ ADL (Argument-Dependent Lookup): Namespaces & Operators
- C++ Name Mangling Explained: Symbols, extern C
- C++ User-Defined Literals: Raw vs Cooked Operators, Unit Types and consteval Validation
- C++ Interface Design and PIMPL: Cut Compile Dependencies