The C++11 standard has introduced a new type of namespace called inline namespaces that are basically a mechanism that makes declarations from a nested namespace look and act like they were part of the surrounding namespace. Inline namespaces are declared using the inline keyword in the namespace declaration (unnamed namespaces can also be inlined). This is a helpful feature for library versioning, and in this recipe, we will see how inline namespaces can be used for versioning symbols. From this recipe, you will learn how to version your source code using inline namespaces and conditional compilation.
Using inline namespaces for symbol versioning
Getting ready
In this recipe, we will discuss namespaces and nested namespaces, templates and template specializations, and conditional compilation using preprocessor macros. Familiarity with these concepts is required in order to proceed with the recipe.
How to do it...
To provide multiple versions of a library and let the user decide what version to use, do the following:
- Define the content of the library inside a namespace.
- Define each version of the library or parts of it inside an inner inline namespace.
- Use preprocessor macros and #if directives to enable a particular version of the library.
The following example shows a library that has two versions that clients can use:
namespace modernlib
{
#ifndef LIB_VERSION_2
inline namespace version_1
{
template<typename T>
int test(T value) { return 1; }
}
#endif
#ifdef LIB_VERSION_2
inline namespace version_2
{
template<typename T>
int test(T value) { return 2; }
}
#endif
}
How it works...
A member of an inline namespace is treated as if it was a member of the surrounding namespace. Such a member can be partially specialized, explicitly instantiated, or explicitly specialized. This is a transitive property, which means that if a namespace A contains an inline namespace B that contains an inline namespace C, then the members of C appear as they were members of both B and A and the members of B appear as they were members of A.
To better understand why inline namespaces are helpful, let's consider the case of developing a library that evolves over time from a first version to a second version (and further on). This library defines all its types and functions under a namespace called modernlib. In the first version, this library could look like this:
namespace modernlib
{
template<typename T>
int test(T value) { return 1; }
}
A client of the library can make the following call and get back the value 1:
auto x = modernlib::test(42);
However, the client might decide to specialize the template function test() as the following:
struct foo { int a; };
namespace modernlib
{
template<>
int test(foo value) { return value.a; }
}
auto y = modernlib::test(foo{ 42 });
In this case, the value of y is no longer 1, but 42 because the user-specialized function gets called.
Everything is working correctly so far, but as a library developer you decide to create a second version of the library, yet still ship both the first and the second version and let the user control what to use with a macro. In this second version, you provide a new implementation of the test() function that no longer returns 1, but 2. To be able to provide both the first and second implementations, you put them in nested namespaces called version_1 and version_2 and conditionally compile the library using preprocessor macros:
namespace modernlib
{
namespace version_1
{
template<typename T>
int test(T value) { return 1; }
}
#ifndef LIB_VERSION_2
using namespace version_1;
#endif
namespace version_2
{
template<typename T>
int test(T value) { return 2; }
}
#ifdef LIB_VERSION_2
using namespace version_2;
#endif
}
Suddenly, the client code will break, regardless of whether it uses the first or second version of the library. That is because the test function is now inside a nested namespace, and the specialization for foo is done in the modernlib namespace, when it should actually be done in modernlib::version_1 or modernlib::version_2. This is because the specialization of a template is required to be done in the same namespace where the template was declared. In this case, the client needs to change the code like this:
#define LIB_VERSION_2
#include "modernlib.h"
struct foo { int a; };
namespace modernlib
{
namespace version_2
{
template<>
int test(foo value) { return value.a; }
}
}
This is a problem because the library leaks implementation details, and the client needs to be aware of those in order do the template specialization. These internal details are hidden with inline namespaces in the manner shown in the How to do it... section of this recipe. With that definition of the modernlib library, the client code with the specialization of the test() function in the modernlib namespace is no longer broken, because either version_1::test() or version_2::test() (depending on what version the client is actually using) acts as being part of the enclosing modernlib namespace when template specialization is done. The details of the implementation are now hidden to the client that only sees the surrounding namespace modernlib.
However, you should keep in mind that:
- The namespace std is reserved for the standard and should never be inlined.
- A namespace should not be defined inline if it was not inline in its first definition.
See also
- Using unnamed namespaces instead of static globals
- Conditionally compiling your source code recipe of Chapter 4, Preprocessor and Compilation