Reader small image

You're reading from  Modern C++ Programming Cookbook - Third Edition

Product typeBook
Published inFeb 2024
PublisherPackt
ISBN-139781835080542
Edition3rd Edition
Right arrow
Author (1)
Marius Bancila
Marius Bancila
author image
Marius Bancila

Marius Bancila is a software engineer with two decades of experience in developing solutions for line of business applications and more. He is the author of The Modern C++ Challenge and Template Metaprogramming with C++. He works as a software architect and is focused on Microsoft technologies, mainly developing desktop applications with C++ and C#. He is passionate about sharing his technical expertise with others and, for that reason, he has been recognized as a Microsoft MVP for C++ and later developer technologies since 2006. Marius lives in Romania and is active in various online communities.
Read more about Marius Bancila

Right arrow

Using inline namespaces for symbol versioning

The C++11 standard has introduced a new type of namespace called inline namespaces, which 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 learn 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.

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 this 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 were 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 follows:

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 instead 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 instead.

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 breaks, regardless of whether it uses the first or second version of the library. This 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 to do 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 actually uses) acts as if it is part of the enclosing modernlib namespace when template specialization is done. The details of the implementation are now hidden to the client, who 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. Also, 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, to explore anonymous namespaces and learn how they help
  • Chapter 4, Conditionally compiling your source code, to learn the various options for performing conditional compilation
Previous PageNext Page
You have been reading a chapter from
Modern C++ Programming Cookbook - Third Edition
Published in: Feb 2024Publisher: PacktISBN-13: 9781835080542
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime

Author (1)

author image
Marius Bancila

Marius Bancila is a software engineer with two decades of experience in developing solutions for line of business applications and more. He is the author of The Modern C++ Challenge and Template Metaprogramming with C++. He works as a software architect and is focused on Microsoft technologies, mainly developing desktop applications with C++ and C#. He is passionate about sharing his technical expertise with others and, for that reason, he has been recognized as a Microsoft MVP for C++ and later developer technologies since 2006. Marius lives in Romania and is active in various online communities.
Read more about Marius Bancila