inline namespace
Transparently Nested Namespaces
An inline namespace is a nested namespace whose member entities closely behave as if they were declared directly within the enclosing namespace.
Description
To a first approximation, an inline namespace (e.g., v2 in the code snippet below) acts a lot like a conventional nested namespace (e.g., v1) followed by a using directive for that namespace in its enclosing namespace1:
// example.cpp: namespace n { namespace v1 // conventional nested namespace followed by using directive { struct T { }; // nested type declaration (identified as ::n::v1::T) int d; // ::n::v1::d at, e.g., 0x01a64e90 } using namespace v1; // Import names T and d into namespace n. } namespace n { inline namespace v2 // similar to being followed by using namespace v2 { struct T { }; // nested type declaration (identified as ::n::v2::T) int d; // ::n::v2::d at, e.g., 0x01a64e94 } // using namespace v2; // redundant when used with an inline namespace }
Four subtle details distinguish these approaches.
Name collisions with existing names behave differently due to differing name-lookup rules.
Argument-dependent lookup (ADL) gives special treatment to inline namespaces.
Template specializations can refer to the primary template in an inline namespace even if written in the enclosing namespace.
Reopening namespaces might reopen an inline namespace.
One important aspect that all forms of namespaces share, however, is that (1) nested symbolic names (e.g., n::v1::T) at the API level, (2) mangled names (e.g., _ZN1n2v11dE, _ZN1n2v21dE), and (3) assigned relocatable addresses (e.g., 0x01a64e90, 0x01a64e94) at the ABI level remain unaffected by the use of either inline or using or both. To be precise, source files containing, alternately, namespace n { inline namespace v { int d; } } and namespace n { namespace v { int d; } using namespace v; }, will produce identical assembly.2 Note that a using directive immediately following an inline namespace is superfluous; name lookup will always consider names in inline namespaces before those imported by a using directive. Such a directive can, however, be used to import the contents of an inline namespace to some other namespace, albeit only in the conventional, using directive sense; see Annoyances — Only one namespace can contain any given inline namespace on page 1082.
More generally, each namespace has what is called its inline namespace set, which is the transitive closure of all inline namespaces within the namespace. All names in the inline namespace set are roughly intended to behave as if they are defined in the enclosing namespace. Conversely, each inline namespace has an enclosing namespace set that comprises all enclosing namespaces up to and including the first noninline namespace.
Loss of access to duplicate names in enclosing namespace
When both a type and a variable are declared with the same name in the same scope, the variable name hides the type name — such behavior can be demonstrated by using the form of sizeof that accepts a nonparenthesized expression (recall that the form of sizeof that accepts a type as its argument requires parentheses):
struct A { double d; }; static_assert(sizeof( A) == 8, ""); // type // static_assert(sizeof A == 8, ""); // Error int A; static_assert(sizeof( A) == 4, ""); // data static_assert(sizeof A == 4, ""); // OK
Unless both type and variable entities are declared within the same scope, no preference is given to variable names; the name of an entity in an inner scope hides a like-named entity in an enclosing scope:
void f() { double B; static_assert(sizeof(B) == 8, ""); // variable { static_assert(sizeof(B) == 8, ""); // variable struct B { int d; }; static_assert(sizeof(B) == 4, ""); // type } static_assert(sizeof(B) == 8, ""); // variable }
When an entity is declared in an enclosing namespace and another entity having the same name hides it in a lexically nested scope, then (apart from inline namespaces) access to a hidden element can generally be recovered by using scope resolution:
struct C { double d; }; static_assert(sizeof( C) == 8, ""); void g() { static_assert(sizeof( C) == 8, ""); // type int C; static_assert(sizeof( C) == 4, ""); // variable static_assert(sizeof(::C) == 8, ""); // type } static_assert(sizeof( C) == 8, ""); // type
A conventional nested namespace behaves as one might expect:
namespace outer { struct D { double d; }; static_assert(sizeof( D) == 8, ""); // type namespace inner { static_assert(sizeof( D) == 8, ""); // type int D; static_assert(sizeof( D) == 4, ""); // var } static_assert(sizeof( D) == 8, ""); // type static_assert(sizeof(inner::D) == 4, ""); // var static_assert(sizeof(outer::D) == 8, ""); // type using namespace inner;//static_assert(sizeof( D) == 0, ""); // Error static_assert(sizeof(inner::D) == 4, ""); // var static_assert(sizeof(outer::D) == 8, ""); // type } static_assert(sizeof(outer::D) == 8, ""); // type
In the example above, the inner variable name, D, hides the outer type with the same name, starting from the point of D’s declaration in inner until inner is closed, after which the unqualified name D reverts to the type in the outer namespace. Then, right after the subsequent using namespace inner; directive, the meaning of the unqualified name D in outer becomes ambiguous, shown here with a static_assert that is commented out; any attempt to refer to an unqualified D from here to the end of the scope of outer will fail to compile. The type entity declared as D in the outer namespace can, however, still be accessed — from inside or outside of the outer namespace, as shown in the example — via its qualified name, outer::D.
If an inline namespace were used instead of a nested namespace followed by a using directive, however, the ability to recover by name the hidden entity in the enclosing namespace is lost. Unqualified name lookup considers the inline namespace set and the used namespace set simultaneously. Qualified name lookup first considers the inline namespace set and then goes on to look into used namespaces. These lookup rules mean we can still refer to outer::D in the example above, but doing so would still be ambiguous if inner were an inline namespace. This subtle difference in behavior is a byproduct of the highly specific use case that motivated this feature and for which it was explicitly designed; see Use Cases — Link-safe ABI versioning on page 1067.
Argument-dependent–lookup interoperability across inline namespace boundaries
Another important aspect of inline namespaces is that they allow ADL to work seamlessly across inline namespace boundaries. Whenever unqualified function names are being resolved, a list of associated namespaces is built for each argument of the function. This list of associated namespaces comprises the namespace of the argument, its enclosing namespace set, plus the inline namespace set.
Consider the case of a type, U, defined in an outer namespace, and a function, f(U), declared in an inner namespace nested within outer. A second type, V, is defined in the inner namespace, and a function, g, is declared, after the close of inner, in the outer namespace:
namespace outer { struct U { }; // inline // Uncommenting this line fixes the problem. namespace inner { void f(U) { } struct V { }; } using namespace inner; // If we inline inner, we don't need this line. void g(V) { } } void client() { f(outer::U()); // Error, f is not declared in this scope. g(outer::inner::V()); // Error, g is not declared in this scope. }
In the example above, a client invoking f with an object of type outer::U fails to compile because f(outer::U) is declared in the nested inner namespace, which is not the same as declaring it in outer. Because ADL does not look into namespaces added with the using directive, ADL does not find the needed outer::inner::f function. Similarly, the type V, defined in namespace outer::inner, is not declared in the same namespace as the function g that operates on it. Hence, when g is invoked from within client on an object of type outer::inner::V, ADL again does not find the needed function outer::g(outer::V).
Simply making the inner namespace inline solves both of these ADL-related problems. All transitively nested inline namespaces — up to and including the most proximate non-inline enclosing namespace — are treated as one with respect to ADL.
The ability to specialize templates declared in a nested inline namespace
The third property that distinguishes inline namespaces from conventional ones, even when followed by a using directive, is the ability to specialize a class template defined within an inline namespace from within an enclosing one; this ability holds transitively up to and including the most proximate noninline namespace:
namespace out // proximate noninline outer namespace { inline namespace in1 // first-level nested inline namespace { inline namespace in2 // second-level nested inline namespace { template <typename T> // primary class template general definition struct S { }; template <> // class template full specialization struct S<char> { }; } template <> // class template full specialization struct S<short> { }; } template <> // class template full specialization struct S<int> { }; } using namespace out; // conventional using directive template <> struct S<int> { }; // Error, cannot specialize from this scope
Note that the conventional nested namespace out followed by a using directive in the enclosing namespace does not admit specialization from that outermost namespace, whereas all of the inline namespaces do. Function templates behave similarly except that — unlike class templates, whose definitions must reside entirely within the namespace in which they are declared — a function template can be declared within a nested namespace and then be defined from anywhere via a qualified name:
namespace out // proximate noninline outer namespace { inline namespace in1 // first-level nested inline namespace { template <typename T> // function template declaration void f(); template <> // function template (full) specialization void f<short>() { } } template <> // function template (full) specialization void f<int>() { } } template <typename T> // function template general definition void out::in1::f() { }
An important takeaway from the examples above is that every template entity — be it class or function — must be declared in exactly one place within the collection of namespaces that comprise the inline namespace set. In particular, declaring a class template in a nested inline namespace and then subsequently defining it in a containing namespace is not possible because, unlike a function definition, a type definition cannot be placed into a namespace via name qualification alone:
namespace outer { inline namespace inner { template <typename T> // class template declaration struct Z; // (if defined, must be within same namespace) template <> // class template full specialization struct Z<float> { }; } template <typename T> // inconsistent declaration (and definition) struct Z { }; // Z is now ambiguous in namespace outer. const int i = sizeof(Z<int>); // Error, reference to Z is ambiguous. template <> // attempted class template full specialization struct Z<double> { }; // Error, outer::Z or outer::inner::Z? }
Reopening namespaces can reopen nested inline ones
Another subtlety specific to inline namespaces is related to reopening namespaces. Consider a namespace outer that declares a nested namespace outer::m and an inline namespace inner that, in turn, declares a nested namespace outer:inner::m. In this case, subsequent attempts to reopen namespace m cause an ambiguity error:
namespace outer { namespace m { } // opens and closes ::outer::m inline namespace inner { namespace n { } // opens and closes ::outer::inner::n namespace m { } // opens and closes ::outer::inner::m } namespace n // OK, reopens ::outer::inner::n { struct S { }; // defines ::outer::inner::n::S } namespace m // Error, namespace m is ambiguous. { struct T { }; // with clang defines ::outer::m::T } } static_assert(std::is_same outer::n::S, outer::inner::n::S>::value, "");
In the code snippet above, no issue occurs with reopening outer::inner::n and no issue would have occurred with reopening outer::m but for the inner namespaces having been declared inline. When a new namespace declaration is encountered, a lookup determines if a matching namespace having that name appears anywhere in the inline namespace set of the current namespace. If the namespace is ambiguous, as is the case with m in the example above, one can get the surprising error shown.3 If a matching namespace is found inline namespace Chapter 3 Unsafe Features unambiguously inside an inline namespace, n in this case, then it is that nested namespace that is reopened — here, ::outer::inner::n. The inner namespace is reopened even though the last declaration of n is not lexically scoped within inner. Notice that the definition of S is perhaps surprisingly defining ::outer::inner::n::S, not ::outer::n::S. For more on what is not supported by this feature, see Annoyances — Inability to redeclare across namespaces impedes code factoring on page 1079.
Use Cases
Facilitating API migration
Getting a large codebase to promptly upgrade to a new version of a library in any sort of timely fashion can be challenging. As a simplistic illustration, imagine that we have just developed a new library, parselib, comprising a class template, Parser, and a function template, analyze, that takes a Parser object as its only argument:
namespace parselib { template <typename T> class Parser { // ... public: Parser(); int parse(T* result, const char* input); // Load result from null-terminated input; return 0 (on // success) or nonzero (with no effect on result). }; template <typename T> double analyze(const Parser T>& parser); }
To use our library, clients will need to specialize our Parser class directly within the parselib namespace:
struct MyClass { /*...*/ }; // end-user-defined type namespace parselib // necessary to specialize Parser { template <> // Create full specialization of class class<Parser MyClass> // Parser for user-type MyClass. { // ... public: Parser(); int parse(MyClass* result, const char* input); // The contract for a specialization typically remains the same. }; double analyze(const Parser<MyClass>& parser); }
Typical client code will also look for the Parser class directly within the parselib namespace:
void client() { MyClass result; parselib::Parser<MyClass> parser; int status = parser.parse(&result, "...( MyClass value )..."); if (status != 0) { return; } double value = analyze(parser); // ... }
Note that invoking analyze on objects of some instantiated type of the Parser class template will rely on ADL to find the corresponding overload.
We anticipate that our library’s API will evolve over time, so we want to enhance the design of parselib accordingly. One of our goals is to somehow encourage clients to move essentially all at once, yet also to accommodate both the early adopters and the inevitable stragglers that make up a typical adoption curve. Our approach will be to create, within our outer parselib namespace, a nested inline namespace, v1, which will hold the current implementation of our library software:
namespace parselib { inline namespace v1 // Note our use of inline namespace here. { template <typename T> class Parser { // ... public: Parser(); int parse(T* result, const char* input); // Load result from null-terminated input; return 0 (on // success) or nonzero (with no effect on result). }; template <typename T> double analyze(const Parser<T>& parser); } }
As suggested by the name v1, this namespace serves primarily as a mechanism to support library evolution through API and ABI versioning (see Link-safe ABI versioning on page 1067 and Build modes and ABI link safety on page 1071). The need to specialize class Parser and, independently, the reliance on ADL to find the free function template analyze require the use of inline namespaces, as opposed to a conventional namespace followed by a using directive.
Note that, whenever a subsystem starts out directly in a first-level namespace and is subsequently moved to a second-level nested namespace for the purpose of versioning, declaring the inner namespace inline is the most reliable way to avoid inadvertently destabilizing existing clients; see also Enabling selective using directives for short-named entities on page 1074.
Now suppose we decide to enhance parselib in a non–backwards-compatible manner, such that the signature of parse takes a second argument size of type std::size_t to allow parsing of non–null-terminated strings and to reduce the risk of buffer overruns. Instead of unilaterally removing all support for the previous version in the new release, we can create a second namespace, v2, containing the new implementation and then, at some point, make v2 the inline namespace instead of v1:
#include <cstddef> // std::size_t namespace parselib { namespace v1 // Notice that v1 is now just a nested namespace. { template <typename T> class Parser { // ... public: Parser(); int parse(T* result, const char* input); // Load result from null-terminated input; return 0 (on // success) or nonzero (with no effect on result). }; template <typename T> double analyze(const Parser<T>& parser); } inline namespace v2 // Notice that use of inline keyword has moved here. { template <typename T> class Parser { // ... public: // Note incompatible change to Parser's essential API. Parser(); int parse(T* result, const char* input, std::size_t size); // Load result from input of specified size; return 0 // on success) or nonzero (with no effect on result). }; template <typename T> double analyze(const Parser<T>& parser); } }
When we release this new version with v2 made inline, all existing clients that rely on the version supported directly in parselib will, by design, break when they recompile. At that point, each client will have two options. The first one is to upgrade the code immediately by passing in the size of the input string (e.g., 23) along with the address of its first character:
void client() { // ... int status = parser.parse(&result, "...( MyClass value )...", 23); // ... ^^^^ Look here! }
The second option is to change all references to parselib to refer to the original version in v1 explicitly:
namespace parselib { namespace v1 // specializations moved to nested namespace { template <> class Parser <MyClass> { // ... public: Parser(); int parse(MyClass* result, const char* input); }; double analyze(const Parser <MyClass>& parser); } } void client1() { MyClass result; parselib::v1::Parser <MyClass> parser; // reference nested namespace v1 int status = parser.parse(&result, "...( MyClass value )..."); if (status != 0) { return; } double value = analyze(parser); // ... }
Providing the updated version in a new inline namespace v2 provides a more flexible migration path — especially for a large population of independent client programs — compared to manual targeted changes in client code.
Although new users would pick up the latest version automatically either way, existing users of parselib will have the option of converting immediately by making a few small syntactic changes or opting to remain with the original version for a while longer by making all references to the library namespace refer explicitly to the desired version. If the library is released before the inline keyword is moved, early adopters will have the option of opting in by referring to v2 explicitly until it becomes the default. Those who have no need for enhancements can achieve stability by referring to a particular version in perpetuity or until it is physically removed from the library source.
Although this same functionality can sometimes be realized without using inline namespaces (i.e., by adding a using namespace directive at the end of the parselib namespace), any benefit of ADL and the ability to specialize templates from within the enclosing parselib namespace itself would be lost. Note that, because specialization doesn’t kick in until overload resolution is completed, specializing overloaded functions is dubious at best; see Potential Pitfalls — Relying on inline namespaces to solve library evolution on page 1077.
Providing separate namespaces for each successive version has an additional advantage in an entirely separate dimension: avoiding inadvertent, difficult-to-diagnose, latent linkage defects. Though not demonstrated by this specific example, cases do arise where simply changing which of the version namespaces is declared inline might lead to an ill formed, no-diagnostic required (IFNDR) program. This issue might ensue when one or more of its translation units that use the library are not recompiled before the program is relinked to the new static or dynamic library containing the updated version of the library software; see Link-safe ABI versioning below.
For distinct nested namespaces to guard effectively against accidental link-time errors, the symbols involved have to (1) reside in object code (e.g., a header-only library would fail this requirement) and (2) have the same name mangling (i.e., linker symbol) in both versions. In this particular instance, however, the signature of the parse member function of parser did change, and its mangled name will consequently change as well; hence the same undefined symbol link error would result either way.
Link-safe ABI versioning
inline namespaces are not intended as a mechanism for source-code versioning; instead, they prevent programs from being ill formed due to linking some version of a library with client code compiled using some other, typically older version of the same library. Below, we present two examples: a simple pedagogical example to illustrate the principle followed by a more real-world example. Suppose we have a library component my_thing that implements an example type, Thing, which wraps an int and initializes it with some value in its default constructor defined out-of-line in the cpp file:
struct Thing // version 1 of class Thing { int i; // integer data member (size is 4) Thing(); // original noninline constructor (defined in .cpp file) };
Compiling a source file with this version of the header included might produce an object file that can be incompatible yet linkable with an object file resulting from compiling a different source file with a different version of this header included:
struct Thing // version 2 of class Thing { double d; // double-precision floating-point data member (size is 8) Thing(); // updated noninline constructor (defined in .cpp file) };
To make the problem that we are illustrating concrete, let’s represent the client as a main program that does nothing but create a Thing and print the value of its only data member, i.
// main.cpp: #include <my_thing.h> // my::Thing (version 1) #include <iostream> // std::cout int main() { my::Thing t; std::cout << t.i << '\n'; }
If we compile this program, a reference to a locally undefined linker symbol, such as _ZN2my7impl_v15ThingC1Ev,4 which represents the my::Thing::Thing constructor, will be generated in the main.o file:
$ g++ -c main.cpp
Without explicit intervention, the spelling of this linker symbol would be unaffected by any subsequent changes made to the implementation of my::Thing, such as its data members or implementation of its default constructor, even after recompiling. The same, of course, applies to its definition in a separate translation unit.
We now turn to the translation unit implementing type my::Thing. The my_thing component consists of a .h/.cpp pair: my_thing.h and my_thing.cpp. The header file my_thing.h provides the physical interface, such as the definition of the principal type, Thing, its member and associated free function declarations, plus definitions for inline functions and function templates, if any:
// my_thing.h: #ifndef INCLUDED_MY_THING #define INCLUDED_MY_THING namespace my // outer namespace (used directly by clients) { inline namespace impl_v1 // inner namespace (for implementer use only) { struct Thing { int i; // original data member, size = 4 Thing(); // default constructor (defined in my_thing.cpp) }; } } #endif
The implementation file my_thing.cpp contains all of the noninline function bodies that will be translated separately into the my_thing.o file:
// my_thing.cpp: #include <my_thing.h> namespace my // outer namespace (used directly by clients) { inline namespace impl_v1 // inner namespace (for implementer use only) { Thing::Thing() : i(0) // Load a 4-byte value into Thing's data member. { } } }
Observing common good practice, we include the header file of the component as the first substantive line of code to ensure that — irrespective of anything else — the header always compiles in isolation, thereby avoiding insidious include-order dependencies.5 When we compile the source file my_thing.cpp, we produce an object file my_thing.o containing the definition of the same linker symbol, such as _ZN2my7impl_v15ThingC1Ev, for the default constructor of my::Thing needed by the client:
$ g++ -c my_thing.cpp
We can then link main.o and my_thing.o into an executable and run it:
$ g++ -o prog main.o my_thing.o $ ./prog 0
Now, suppose we were to change the definition of my::Thing to hold a double instead of an int, recompile my_thing.cpp, and then relink with the original main.o without recompiling main.cpp first. None of the relevant linker symbols would change, and the code would recompile and link just fine, but the resulting binary prog would be IFNDR: the client would be trying to print a 4-byte, int data member, i, in main.o that was loaded by the library component as an 8-byte, double into d in my_thing.o. We can resolve this problem by changing — or, if we didn’t think of it in advance, by adding — a new inline namespace and making that change there:
// my_thing.cpp: #include <my_thing.h> namespace my // outer namespace (used directly by clients) { inline namespace impl_v2 // inner namespace (for implementer use only) { Thing::Thing() : d(0.0) // Load 8-byte value into Thing's data member. { } } }
Now clients that attempt to link against the new library will not find the linker symbol, such as _Z...impl_v1...v, and the link stage will fail. Once clients recompile, however, the undefined linker symbol will match the one available in the new my_thing.o, such as _Z...impl_v2...v, the link stage will succeed, and the program will again work as expected. What’s more, we have the option of keeping the original implementation. In that case, existing clients that have not as yet recompiled will continue to link against the old version until it is eventually removed after some suitable deprecation period.
As a more realistic second example of using inline namespaces to guard against linking incompatible versions, suppose we have two versions of a Key class in a security library in the enclosing namespace, auth — the original version in a regular nested namespace v1, and the new current version in an inline nested namespace v2:
#include <cstdint> // std::uint32_t, std::unit64_t namespace auth // outer namespace (used directly by clients) { namespace v1 // inner namespace (optionally used by clients) { class Key { private: std::uint32_t d_key; // sizeof(Key) is 4 bytes. public: std::uint32_t key() const; // stable interface function // ... }; } inline namespace v2 // inner namespace (default current version) { class Key { private: std::uint64_t d_securityHash; std::uint32_t d_key; // sizeof(Key) is 16 bytes. public: std::uint32_t key() const; // stable interface function // ... }; } }
Attempting to link together older binary artifacts built against version 1 with binary artifacts built against version 2 will result in a link-time error rather than allowing an ill formed program to be created. Note, however, that this approach works only if functionality essential to typical use is defined out of line in a .cpp file. For example, it would add absolutely no value for libraries that are shipped entirely as header files, since the versioning offered here occurs strictly at the binary level (i.e., between object files) during the link stage.
Build modes and ABI link safety
In certain scenarios, a class might have two different memory layouts depending on compilation flags. For instance, consider a low-level ManualBuffer class template in which an additional data member is added for debugging purposes:
template <typename T> struct ManualBuffer { private: alignas(T) char d_data[sizeof(T)]; // aligned and big enough to hold a T #ifndef NDEBUG bool d_engaged; // tracks whether buffer is full (debug builds only) #endif public: void construct(const T& obj); // Emplace obj. (Engage the buffer.) The behavior is undefined unless // the buffer was not previously engaged. void destroy(); // Destroy the current obj. (Disengage the buffer.) The behavior is // undefined unless the buffer was previously engaged. // ... };
Note that we have employed the C++11 alignas attribute (see Section 2.1.“alignas” on page 168) here because it is exactly what’s needed for this usage example.
The d_engaged flag in the example above serves as a way to detect misuse of the ManualBuffer class but only in debug builds. The extra space and run time required to maintain this Boolean flag is undesirable in a release build because ManualBuffer is intended to be an efficient, lightweight abstraction over the direct use of placement new and explicit destruction.
The linker symbol names generated for the methods of ManualBuffer are the same irrespective of the chosen build mode. If the same program links together two object files where ManualBuffer is used — one built in debug mode and one built in release mode — the one-definition rule (ODR) will be violated, and the program will again be IFNDR.
Prior to inline namespaces, it was possible to control the ABI-level name of linked symbols by creating separate template instantiations on a per-build-mode basis:
#ifndef NDEBUG enum { is_debug_build = 1 }; #else enum { is_debug_build = 0 }; #endif template <typename T, bool Debug = is_debug_build> struct ManualBuffer { /*...*/ };
While the code above changes the interface of ManualBuffer to accept an additional template parameter, it also allows debug and release versions of the same class to coexist in the same program, which might prove useful, e.g., for testing.
Another way of avoiding incompatibilities at link time is to introduce two inline namespaces, the entire purpose of which is to change the ABI-level names of the linker symbols associated with ManualBuffer depending on the build mode:
#ifndef NDEBUG // perhaps a BAD IDEA inline namespace releasea #else inline namespace debug #endif { template <typename T> struct ManualBuffer { // ... (same as above) }; }
The approach demonstrated in this example tries to ensure that a linker error will occur if any attempt is made to link objects built with a build mode different from that of manualbuffer.o. Tying it to the NDEBUG flag, however, might have unintended consequences; we might introduce unwanted restrictions in what we call mixed-mode builds. Most modern platforms support the notion of linking a collection of object files irrespective of their optimization levels. The same is certainly true for whether or not C-style assert is enabled. In other words, we might want to have a mixed-mode build where we link object files that differ in their optimization and assertion options, as long as they are binary compatible — i.e., in this case, they all must be uniform with respect to the implementation of ManualBuffer. Hence, a more general, albeit more complicated and manual, approach would be to tie the noninteroperable behavior associated with this “safe” or “defensive” build mode to a different switch entirely. Another consideration would be to avoid ever inlining a namespace into the global namespace since no method is available to recover a symbol when there is a collision:
namespace buflib // GOOD IDEA: enclosing namespace for nested inline namespace { #ifdef SAFE_MODE // GOOD IDEA: separate control of non-interoperable versions inline namespace safe_build_mode #else inline namespace normal_build_mode #endif { template <typename T> struct ManualBuffer { private: alignas(T) char d_data[sizeof(T)]; // aligned/sized to hold a T #ifdef SAFE_MODE bool d_engaged; // tracks whether buffer is full (safe mode only) #endif public: void construct(const T& obj); // sets d_engaged (safe mode only) void destroy(); // sets d_engaged (safe mode only) // ... }; } }
And, of course, the appropriate conditional compilation within the function bodies would need to be in the corresponding .cpp file.
Finally, if we have two implementations of a particular entity that are sufficiently distinct, we might choose to represent them in their entirety, controlled by their own bespoke conditional-compilation switches, as illustrated here using the my::VersionedThing type (see Link-safe ABI versioning on page 1067):
// my_versionedthing.h: #ifndef INCLUDED_MY_VERSIONEDTHING #define INCLUDED_MY_VERSIONEDTHING namespace my { #ifdef MY_THING_VERSION_1 // bespoke switch for this component version inline #endif namespace v1 { struct VersionedThing { int d_i; VersionedThing(); }; } #ifdef MY_THING_VERSION_2 // bespoke switch for this component version inline #endif namespace v2 { struct VersionedThing { double d_i; VersionedThing(); }; } } #endif
However, see Potential Pitfalls— inline-namespace–based versioning doesn’t scale on page 1076.
Enabling selective using directives for short-named entities
Introducing a large number of small names into client code that doesn’t follow rigorous nomenclature can be problematic. Hoisting these names into one or more nested namespaces so that they are easier to identify as a unit and can be used more selectively by clients, such as through explicit qualification or using directives, can sometimes be an effective way of organizing shared codebases. For example, std::literals and its nested namespaces, such as chrono_literals, were introduced as inline namespaces in C++14. As it turns out, clients of these nested namespaces have no need to specialize any templates defined in these namespaces nor do they define types that must be found through ADL, but one can at least imagine special circumstances in which such tiny-named entities are either templates that require specialization or operator-like functions, such as swap, defined for local types within those nested namespaces. In those cases, inline namespaces would be required to preserve the desired “as if” properties.
Even without either of these two needs, another property of an inline namespace differentiates it from a noninline one followed by a using directive. Recall from Description — Loss of access to duplicate names in enclosing namespace on page 1056 that a name in an outer namespace will hide a duplicate name imported via a using directive, whereas any access to that duplicate name within the enclosing namespace would be ambiguous when that symbol is installed by way of an inline namespace. To see why this more forceful clobbering behavior might be preferred over hiding, suppose we have a communal namespace abc that is shared across multiple disparate headers. The first header, abc_header1.h, represents a collection of logically related small functions declared directly in abc:
// abc_header1.h: namespace abc { int i(); int am(); int smart(); }
A second header, abc_header2.h, creates a suite of many functions having tiny function names. In a perhaps misguided effort to avoid clobbering other symbols within the abc namespace having the same name, all of these tiny functions are sequestered within a nested namespace:
// abc_header2.h: namespace abc { namespace nested // Should this namespace have been inline instead? { int a(); // lots of functions with tiny names int b(); int c(); // ... int h(); int i(); // might collide with another name declared in abc // ... int z(); } using namespace nested; // becomes superfluous if nested is made inline }
Now suppose that a client application includes both of these headers to accomplish some task:
// client.cpp: #include <abc_header1.h> #include <abc_header2.h> int function() { if (abc::smart() < 0) { return -1; } // uses smart() from abc_header1.h return abc::z() + abc::i() + abc::a() + abc::h() + abc::c(); // Oops! // Bug, silently uses the abc::i() defined in abc_header1.h }
In trying to cede control to the client as to whether the declared or imported abc::i() function is to be used, we have, in effect, invited the defect illustrated in the above example whereby the client was expecting the abc::i() from abc_header2.h and yet picked up the one from abc_header1.h by default. Had the nested namespace in abc_header2.h been declared inline, the qualified name abc::i() would have automatically been rendered ambiguous in namespace abc, the translation would have failed safely, and the defect would have been exposed at compile time. The downside, however, is that no method would be available to recover nominal access to the abc::i() defined in abc_header1.h once abc_header2.h is included, even though the two functions (e.g., including their mangled names at the ABI level) remain distinct.
Potential Pitfalls
inline-namespace–based versioning doesn’t scale
The problem with using inline namespaces for ABI link safety is that the protection they offer is only partial; in a few major places, critical problems can linger until run time instead of being caught at compile time.
Controlling which namespace is inline using macros, such as was done in the my::VersionedThing example in Use Cases — Link-safe ABI versioning on page 1067, will result in code that directly uses the unversioned name, my::VersionedThing being bound directly to the versioned name my::v1::VersionedThing or my::v2::VersionedThing, along with the class layout of that particular entity. Sometimes details of using the inline namespace member are not resolved by the linker, such as the object layout when we use types from that namespace as member variables in other objects:
// my_thingaggregate.h: // ... #include <my_versionedthing.h> // ... namespace my { struct ThingAggregate { // ... VersionedThing d_thing; // ... }; }
This new ThingAggregate type does not have the versioned inline namespace as part of its mangled name; it does, however, have a completely different layout if built with MY_THING_VERSION_1 defined versus MY_THING_VERSION_2 defined. Linking a program with mixed versions of these flags will result in runtime failures that are decidedly difficult to diagnose.
This same sort of problem will arise for functions taking arguments of such types; calling a function from code that is wrong about the layout of a particular type will result in stack corruption and other undefined and unpredictable behavior. This macro-induced problem will also arise in cases where an old object file is linked against new code that changes which namespace is inlined but still provides the definitions for the old version namespace. The old object file for the client can still link, but new object files using the headers for the old objects might attempt to manipulate those objects using the new namespace.
The only viable workaround for this approach is to propagate the inline namespace hierarchy through the entire software stack. Every object or function that uses my::VersionedThing needs to also be in a namespace that differs based on the same control macro. In the case of ThingAggregate, one could just use the same my::v1 and my::v2 namespaces, but higher-level libraries would need their own my-specific nested namespaces. Even worse, for higher-level libraries, every lower-level library having a versioning scheme of this nature would need to be considered, resulting in having to provide the full cross-product of nested namespaces to get link-time protection against mixed-mode builds.
This need for layers above a library to be aware of and to integrate into their own structure the same namespaces the library has removes all or most of the benefits of using inline namespaces for versioning. For an authentic real-world case study of heroic industrial use — and eventual disuse — of inline-namespaces for versioning, see Appendix — Case study of using inline namespaces for versioning on page 1083.
Relying on inline namespaces to solve library evolution
Inline namespaces might be misperceived as a complete solution for the owner of a library to evolve its API. As an especially relevant example, consider the C++ Standard Library, which itself does not use inline namespaces for versioning. Instead, to allow for its anticipated essential evolution, the Standard Library imposes certain special restrictions on what is permitted to occur within its own std namespace by dint of deeming certain problematic uses as either ill formed or otherwise engendering undefined behavior.
Since C++11, several restrictions related to the Standard Library were put in place.
Users may not add any new declarations within namespace std, meaning that users cannot add new functions, overloads, types, or templates to std. This restriction gives the Standard Library freedom to add new names in future versions of the Standard.
Users may not specialize member functions, member function templates, or member class templates. Specializing any of those entities might significantly inhibit a Standard Library vendor’s ability to maintain its otherwise encapsulated implementation details.
Users may add specializations of top-level Standard Library templates only if the declaration depends on the name of a nonstandard user-defined type and only if that user-defined type meets all requirements of the original template. Specialization of function templates is allowed but generally discouraged because this practice doesn’t scale since function templates cannot be partially specialized. Specializing of standard class templates when the specialization names a nonstandard user-defined type, such as std::vector<MyType*>, is allowed but also problematic when not explicitly supported. While certain specific types, such as std::hash, are designed for user specialization, steering clear of the practice for any other type helps to avoid surprises.
Several other good practices facilitate smooth evolution for the Standard Library.6
Avoid specializing variable templates, even if dependent on user-defined types, except for those variable templates where specialization is explicitly allowed.7
Other than a few specific exceptions, avoiding the forming of pointers to Standard Library functions — either explicitly or implicitly — allows the library to add overloads, either as part of the Standard or as an implementation detail for a particular Standard Library, without breaking user code.8
Overloads of Standard Library functions that depend on user-defined types are permitted, but, as with specializing Standard Library templates, users must still meet the requirements of the Standard Library function. Some functions, such as std::swap, are designed to be customization points via overloading, but leaving functions not specifically designed for this purpose to vendor implementations only helps to avoid surprises.
Finally, upon reading about this inline namespace feature, one might think that all names in namespace std could be made available at a global scope simply by inserting an inline namespace std {} before including any standard headers. This practice is, however, explicitly called out as ill-formed within the C++11 Standard. Although not uniformly diagnosed as an error by all compilers, attempting this forbidden practice is apt to lead to surprising problems even if not diagnosed as an error immediately.
Inconsistent use of inline keyword is ill formed, no diagnostic required
It is an ODR violation, IFNDR, for a nested namespace to be inline in one translation unit and noninline in another. And yet, the motivating use case of this feature relies on the linker to actively complain whenever different, incompatible versions — nested within different, possibly inline-inconsistent, namespaces of an ABI — are used within a single executable. Because declaring a nested namespace inline does not, by design, affect linker-level symbols, developers must take appropriate care, such as effective use of header files, to defend against such preventable inconsistencies.
Annoyances
Inability to redeclare across namespaces impedes code factoring
An essential feature of an inline namespace is the ability to declare a template within a nested inline namespace and then specialize it within its enclosing namespace. For example, we can declare
a type template, S0
a couple of function templates, f0 and g0
and a member function template h0, which is similar to f0
in an inline namespace, inner, and specialize each of them, such as for int, in the enclosing namespace, outer:
namespace outer // enclosing namespace { inline namespace inner // nested namespace { template<typename T> struct S0; // declarations of template<typename T> void f0(); // various class template<typename T> void g0(T v); // and function struct A0 { template <typename T> void h0(); }; // templates } template<> struct S0<int> { }; // specializations template<> void f0<int>() { } // of the various void g0(int) { } /* overload not specialization */ // class and function template<> void A0::h0<int>() { } // declarations above } // in outer namespace
Note that, in the case of g0 in this example, the “specialization” void g0(int) is a non-template overload of the function template g0 rather than a specialization of it. We cannot, however, portably9 declare these templates within the outer namespace and then specialize them within the inner one, even though the inner namespace is inline:
namespace outer // enclosing namespace { template<typename T> struct S1; // class template template<typename T> void f1(); // function template template<typename T> void g1(T v); // function template struct A1 { template <typename T> void h1(); }; // member function template inline namespace inner // nested namespace { // BAD IDEA template<> struct S1<int> { }; // Error, S1 not a template template<> void f1<int>() { } // Error, f1 not a template void g1(int) { } // OK, overloaded function template<> void A1::h1<int>() { } // Error, h1 not a template } }
Attempting to declare a template in the outer namespace and then define, effectively redeclaring, it in an inline inner one causes the name to be inaccessible within the outer namespace:
namespace outer // enclosing namespace { // BAD IDEA template<typename T> struct S2; // declarations of template<typename T> void f2(); // various class and template<typename T> void g2(T v); // function templates inline namespace inner // nested namespace { template<typename T> struct S2 { }; // definitions of template<typename T> void f2() { } // unrelated class and template<typename T> void g2(T v) { } // function templates } template <> struct S2<int> { }; // Error, S2 is ambiguous in outer. template <> void f2<int>() { } // Error, f2 is ambiguous in outer. void g2(int) { } // OK, g2 is an overload definition. }
Finally, declaring a template in the nested inline namespace inner in the example above and then subsequently defining it in the enclosing outer namespace has the same effect of making declared symbols ambiguous in the outer namespace:
namespace outer // enclosing namespace { // BAD IDEA inline namespace inner // nested namespace { template<typename T> struct S3; // declarations of template<typename T> void f3(); // various class template<typename T> void g3(T v); // and function struct A3 { template <typename T> void h3(); }; // templates } template<typename T> struct S3 { }; // definitions of template<typename T> void f3() { } // unrelated class template<typename T> void g3(T v) { } // and function template<typename T> void A3::h3() { } // templates template <> struct S3<int> { }; // Error, S3 is ambiguous in outer. template <> void f3<int>() { } // Error, f3 is ambiguous in outer. void g3(int) { } // OK, g3 is an overload definition. template <> void A3::h3<int>() { } // Error, h2 is ambiguous in outer. }
Note that, although the definition for a member function template must be located directly within the namespace in which it is declared, a class or function template, once declared, may instead be defined in a different scope by using an appropriate name qualification:
template <typename T> struct outer::S3 { }; // OK, enclosing namespace template <typename T> void outer::inner::f3() { } // OK, nested namespace template <typename T> void outer::g3(T v) { } // OK, enclosing namespace template <typename T> void outer::A3::h3<T>() { } // Error, ill-formed namespace outer { inline namespace inner { template typename T> void A3::h3() { } // OK, within same namespace } }
Also note that, as ever, the corresponding definition of the declared template must have been seen before it can be used in a context requiring a complete type. The importance of ensuring that all specializations of a template have been seen before it is used substantively (i.e., ODR-used) cannot be overstated, giving rise to the only limerick, which is actually part of the normative text, in the C++ Language Standard10:
When writing a specialization,
be careful about its location;
or to make it compile
will be such a trial
as to kindle its self-immolation.
Only one namespace can contain any given inline namespace
Unlike conventional using directives, which can be used to generate arbitrary many-to-many relationships between different namespaces, inline namespaces can be used only to contribute names to the sequence of enclosing namespaces up to the first noninline one. In cases in which the names from a namespace are desired in multiple other namespaces, the classical using directive must be used, with the subtle differences between the two modes properly addressed.
As an example, the C++14 Standard Library provides a hierarchy of nested inline namespaces for literals of different sorts within namespace std.
std::literals::complex_literals
std::literals::chrono_literals
std::literals::string_literals
std::literals::string_view_literals
These namespaces can be imported to a local scope in one shot via a using std::literals or instead, more selectively, by using the nested namespaces directly. This separation of the types used with user-defined literals, which are all in namespace std, from the user-defined literals that can be used to create those types led to some frustration; those who had a using namespace std; could reasonably have expected to get the user-defined literals associated with their std types. However, the types in the nested namespace std::chrono did not meet this expectation.11
Eventually both solutions for incorporating literal namespaces, inline from std::literals and noninline from std::chrono, were pressed into service when, in C++17, a using namespace literals::chrono_literals; was added to the std::chrono namespace. The Standard does not, however, benefit in any objective way from any of these namespaces being inline since the artifacts in the literals namespace neither depend on ADL nor are templates in need of user-defined specializations; hence, having all noninline namespaces with appropriate using declarations would have been functionally indistinguishable from the bifurcated approach taken.
See Also
“alignas” (§2.1, p. 168) provides properly aligned storage for an object of arbitrary type T in the example in Use Cases — Build modes and ABI link safety on page 1071.
Further Reading
sutter14a uses inline namespaces as part of a proposal for a portable ABI across compilers.
lopez-gomez20 uses inline namespaces as part of a solution to avoid ODR violation in an interpreter.
Appendix
Case study of using inline namespaces for versioning
By Niall Douglas
Let me tell you what I (don’t) use them for. It is not a conventional opinion.
At a previous well-regarded company, they were shipping no less than forty-three copies of Boost in their application. Boost was not on the approved libraries list, but the great thing about header-only libraries is that they don’t obviously appear in final binaries, unless you look for them. So each individual team was including bits of Boost quietly and without telling their legal department. Why? Because it saved time. (This was C++98, and boost::shared_ptr and boost::function are both extremely attractive facilities.)
Here’s the really interesting part: Most of these copies of Boost were not the same version. They were varying over a five-year release period. And, unfortunately, Boost makes no API or ABI guarantees. So, theoretically, you could get two different incompatible versions of Boost appearing in the same program binary, and BOOM! there goes memory corruption.
I advocated to Boost that a simple solution would be for Boost to wrap up their implementation into an internal inline namespace. That inline namespace ought to mean something.
lib::v1 is the stable, version-1 ABI, which is guaranteed to be compatible with all past and future lib::v1 ABIs, forever, as determined by the ABI-compliance-check tool that runs on CI. The same goes for v2, v3, and so on.
lib::v2_a7fe42d is the unstable, version-2 ABI, which may be incompatible with any other lib::* ABI; hence, the seven hex chars after the underscore are the git short SHA, permuted by every commit to the git repository but, in practice, per CMake configure, because nobody wants to rebuild everything per commit. This ensures that no symbols from any revision of lib will ever silently collide or otherwise interfere with any other revision of lib, when combined into a single binary by a dumb linker.
I have been steadily making progress on getting Boost to avoid putting anything in the global namespace, so a straightforward find-and-replace can let you “fix” on a particular version of Boost.
That’s all the same as the pitch for inline namespaces. You’ll see the same technique used in libstdc++ and many other major modern C++ codebases.
But I’ll tell you now, I don’t use inline namespaces anymore. Now what I do is use a macro defined to a uniquely named namespace. My build system uses the git SHA to synthesize namespace macros for my namespace name, beginning the namespace and ending the namespace. Finally, in the documentation, I teach people to always use a namespace alias to a macro to denote the namespace:
namespace output = OUTCOME_V2_NAMESPACE;
That macro expands to something like ::outcome_v2_ee9abc2; that is, I don’t use inline namespaces anymore.
Why?
Well, for existing libraries that don’t want to break backward source compatibility, I think inline namespaces serve a need. For new libraries, I think a macro-defined namespace is clearer.
It causes users to publicly commit to “I know what you’re doing here, what it means, and what its consequences are.”
It declares to other users that something unusual (i.e., go read the documentation) is happening here, instead of silent magic behind the scenes.
It prevents accidents that interfere with ADL and other customization points, which induce surprise, such as accidentally injecting a customization point into lib, not into lib::v2.
Using macros to denote namespace lets us reuse the preprocessor machinery to generate C++ modules using the exact same codebase; C++ modules are used if the compiler supports them, else we fall back to inclusion.
Finally, and here’s the real rub, because we now have namespace aliases, if I were tempted to use an inline namespace, nowadays I probably would instead use a uniquely named namespace instead, and, in the include file, I’d alias a user-friendly name to that uniquely named namespace. I think that approach is less likely to induce surprise in the typical developer’s likely use cases than inline namespaces, such as injecting customization points into the wrong namespace.
So now I hope you’ve got a good handle on inline namespaces: I was once keen on them, but after some years of experience, I’ve gone off them in favor of better-in-my-opinion alternatives. Unfortunately, if your type x::S has members of type a::T and macros decide if that is a::v1::T or a::v2::T, then no linker protects the higher-level types from ODR bugs, unless you also version x.