Inline namespaces - C++

14 January 2023

C++11 introduced inline namespaces, but what are inline namespaces?

Contents

What are inline namespaces?

An inline namespaces in like normal namespaces, but uses the inline keyword in its original namespace definition.

Members of an inline namespace are treated as if they are members af the enclosing namespace.

This property is transitive, if a namespace A, contains an inline namespace B, which in turn contains an inline namespace C, then the members of namespace C can be used as though they were members of A or B.

You can compile the following code as an example:

#include <iostream>

namespace A {

inline namespace B {

inline namespace C {

const int foo = 5;

}

} // namespace B

} // namespace A

int main() {
  // what you would normally do with normal namespaces
  std::cout << A::B::C::foo << std::endl; // OK

  // because `namespace C` is `inline`, members of it (`foo` in this case)
  // is treated as if it were a member of the enclosing namespace (`B`)
  std::cout << A::B::foo    << std::endl; // OK

  // `namespace B` is `inline` so member `namespace C` is treated as if it were
  // a member of `A`
  std::cout << A::C::foo    << std::endl; // OK

  // `namespace B` is `inline` so member `namespace C` is treated as if it were
  // a member of `A`, and `namespace C` is also `inline`, so member `foo` is
  // treated as a member of `B`, but `B` was also inline so members are treated
  // as members of `A`
  std::cout << A::foo       << std::endl; // OK

  return 0;
}

Output:

5
5
5
5

Uses of inline namespace

For versioning

According to Bjarne Stroustrup FAQ about inline namespaces, inline namespaces are to allow versioning for backward-compatibility.

You define multiple inner namespaces, and make the most recent one inline. Or anyway, the default one for people who don't care about versioning. The most recent one could be a future or cutting-edge version which is not yet default.

The example given is:

// file V99.h:
inline namespace V99 {
  void f(int);    // does something better than the V98 version
  void f(double); // new feature
  // ...
}

// file V98.h:
namespace V98 {
  void f(int);    // does something
  // ...
}

// file my_library.h:
namespace my_library {
#include "V99.h"
#include "V98.h"
}

// file main.cc
#include "my_library.h"
int main() {
  my_library::V98::f(1);  // old version
  my_library::V99::f(1);  // new version
  my_library::f(1);       // default version (the inline namespace)
  return 0;
}

For versioning based on the standards

We can also use inline namespace for versioning our library for multiple C++ standards using inline namespaces and CPP (C PreProcessor) macros.

One example could be the STL vector.

If we had inline namespaces from the beginning of C++, in C++98 the header of <vector> might have looked like this:

namespace std {

#if __cplusplus < 1997L // pre-standard C++
    inline
#endif

    namespace pre_cxx_1997 {
        template <class T> __vector_impl; // implementation class
        template <class T> // e.g. w/o allocator argument
        class vector : __vector_impl<T> { // private inheritance
            // ...
        };
    }
#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)
#  if __cplusplus == 1997L // C++98/03
    inline
#  endif

    namespace cxx_1997 {

        // std::vector now has an allocator argument
        template <class T, class Alloc=std::allocator<T> >
        class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
            // ...
        };

        // and vector<bool> is special:
        template <class Alloc=std::allocator<bool> >
        class vector<bool> {
            // ...
        };

    };

#endif // C++98/03 or later

} // namespace std

Depending on the value of __cplusplus, either one or the other vector implementation is chosen. If your codebase was written in pre-C++98 times, and you find that the C++98 version of vector is causing trouble for you when you upgrade your compiler, "all" you have to do is to find the references to std::vector in your codebase and replace them by std::pre_cxx_1997::vector.

Come the next standard, and the STL vendor just repeats the procedure again, introducing a new namespace for std::vector with emplace_back support (which requires C++11) and inlining that one if __cplusplus == 201103L.

OK, so why do I need a new language feature for this? I can already do the following to have the same effect, no?

namespace std {

    namespace pre_cxx_1997 {
        // ...
    }
#if __cplusplus < 1997L // pre-standard C++
    using namespace pre_cxx_1997;
#endif

#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)

    namespace cxx_1997 {
        // ...
    };
#  if __cplusplus == 1997L // C++98/03
    using namespace cxx_1997;
#  endif

#endif // C++98/03 or later

} // namespace std

Depending on the value of __cplusplus, I get either one or the other of the implementations.

And you'd be almost correct.

Consider the following valid C++98 user code (it was permitted to fully specialize templates that live in namespace std in C++98 already):

// I don't trust my STL vendor to do this optimisation, so force these 
// specializations myself:
namespace std {
    template <>
    class vector<MyType> : my_special_vector<MyType> {
        // ...
    };
    template <>
    class vector<MyOtherType> : my_special_vector<MyOtherType> {
        // ...
    };
    // ...etc...
} // namespace std

This is perfectly valid code where the user supplies its own implementation of a vector for a set of type where the user apparently knows a more efficient implementation than the one found in (the user copy of) the STL.

But: When specializing a template, you need to do so in the namespace it was declared in. The Standard says that vector is declared in namespace std, so that's where the user rightfully expects to specialize the type.

This code works with a non-versioned namespace std, or with the C++11 inline namespace feature, but not with the versioning trick that used using namespace NESTED, because that exposes the implementation detail that the true namespace in which vector was defined was not std directly.

There are other holes by which you could detect the nested namespace, but inline namespaces plug them all. And that's all there is to it. Immensely useful for the future, but AFAIK the Standard doesn't prescribe inline namespace names for its own standard library (I'd love to be proven wrong on this, though), so it can only be used for third-party libraries, not the standard itself (unless the compiler vendors agree on a naming scheme).

Links

Sources