Chapter 3.6

SF.7: Don’t write using namespace at global scope in a header file

Don’t do this

Please, don’t do this. Ever.

Never. Not ever. Not even once.

Please.

The Core Guidelines document offers the following example:

// bad.h
#include <iostream>
using namespace std; // bad <- "You're not kidding," says Guy

// user.cpp
#include "bad.h"

// some function that happens to be named copy
bool copy(/*... some parameters ...*/);

int main()
{
  // now overloads local ::copy and std::copy, could be ambiguous
  copy(/*...*/);
}

“Bad” is an understatement. We know it seems we are belaboring the point, but this example only exposes part of the horror.

Disambiguation

There is one and only one global namespace. From the standard, [basic.scope.namespace]1:

1. https://eel.is/c++draft/basic.scope.namespace

“The outermost declarative region of a translation unit is also a namespace, called the global namespace. A name declared in the global namespace has global names-pace scope (also called global scope). The potential scope of such a name begins at its point of declaration and ends at the end of the translation unit that is its declara-tive region. A name with global namespace scope is said to be a global name.”

Do not pollute the global namespace: it is a precious resource.

Do not pollute the global namespace: it is a precious resource.

Namespaces are a useful encapsulation tool, collecting together related sym-bols within a single scope and enabling the reuse of names. My favorite example is vector. This has two meanings in my domain: there is the automatically resizing contiguous container, and there is the tuple of numbers representing a mathematical quantity. The former lives in namespace std, while the latter lives somewhere else. In my case, it lives in namespace gdg::maths. Namespace gdg is my personal namespace that I use in all my code, while namespace maths is contained within that and consists of mathematical types and functions, like matrix, vector, normalize, intersect, and all the other types you might need for geometry. I am British, so I say “maths.” Ask me about it one day if you dare.

Having two classes called vector is not a problem, since we can simply apply namespace scope resolution to the symbol, and type std::vector when we want to contain things in the maths namespace and type gdg::maths::vector when we want to manipulate geometric objects outside of the maths namespace.

std:: is not much to type for disambiguation purposes. On the other hand, gdg::maths:: is a little long. It interferes with rapid apprehension of code. The exces-sive colons slightly disrupt reading. Fortunately, we can type

using gdg::maths::vector;

earlier in the scope and the compiler will choose that vector over std::vector when it tries to resolve the symbol. This is known as a using-declaration. It is distinct from typing

using namespace gdg::maths;

which is a using-directive.

The trouble with introducing a using-directive into a header file at global scope is clear for all to see: you have hidden from the user the fact that all the symbols in the namespace have been introduced into the parent scope of every function and class definition. Worse, if you introduce a different using-directive into a different header file and include both of those header files, they become order dependent. You will get subtly different compilation if you change their order.

Unfortunately, this is only the start of the madness.

Using using

The keyword using has four uses. The first is aliasing. For example:

using vector_of_vectors = std::vector<gdg::maths::vector>;

This is very specific. We are introducing a new name as an abbreviation for another name. Here, we are introducing the name vector_of_vectors to directly map to a standard vector of my maths vectors. We are using using in this way to reduce typing and improve clarity.

The second is for importing class members. For example:

struct maths_vector : std::variant<vector<float, 2>, vector<int, 2>> {
  using variant::variant;
}

Here, we are introducing a member of a base class into a derived class. This allows me to construct a maths_vector object using a variant constructor.

The third is for introducing names defined elsewhere into the declarative region of the using-declaration. For example:

namespace gdg::maths {
  using std::inner_product;
}

We can now call the standard version of inner_product from within the gdg::maths namespace without having to prefix it with std. If the name already existed in an outer scope, it is hidden by this new declaration. If we wanted to call a different inner_product function we would have to qualify it.

Using-declarations are more specific than using-directives. They introduce a sin-gle symbol into the current scope. Still, using-declarations should be used with care, at the narrowest comfortable scope. Making a using-declaration may also create an overload set, so make sure it is always easily visible to the client engineer. The danger of making them at file scope is less than that of making a using-directive at file scope, but risks still remain.

The fourth is for making using-directives. These are of very limited use: perhaps you are writing a slide for a presentation and you want to signal to the audience that you are using a particular namespace in some example code. For example:

#include <iostream>
using namespace std;

int main() {
  cout << "Hello, world!";
  return 0;
}

Rather than type std::cout, you are relying on the introduction of all symbols from the std namespace so far declared in the <iostream> header. It is very convenient.

Very, very convenient.

This is a very simple use of a using-directive, suitable for trivial code. In more complicated code surprising things happen, because a using-directive does not only introduce the new symbols into the current scope.

We’ll just say that again: a using-directive does not only introduce new symbols into the current scope. It does something rather more surprising, and we need to cover a little graph theory to work out what’s going on.

Where do the symbols go?

First, we need to look at the idea of a directed acyclic graph, or DAG, a term coined by Bjarne Stroustrup’s office mate, Al Aho. A graph is a set of nodes with relation-ships between them. A directed graph is a set of nodes with relationships between them that only operate in one direction. For example, a class hierarchy is a graph with is-child-of and is-parent-of relationships between classes, which are the nodes. These relationships follow different directions. If we take out the is-child-of relation-ships, we have a directed graph because the relationships work in one direction only. An acyclic graph has an entry and an exit; there is no way to end up back where you started by following the relationships. A class hierarchy of parent relationships is not only directed, but also acyclic.

The same thing can be said of namespaces and containment relationships. A namespace can nest another namespace, but the graph of nesting is a DAG. For example:

namespace World {
  namespace Buildings {
    namespace Municipal {}
    namespace Business {}
  }
}

We can go from World to Municipal by examining nested namespaces, but we can’t go from Municipal to World in the same way.

Second, we need to look at the lowest common ancestor (LCA). The LCA of a pair of nodes is the deepest node common to both. In the above namespace example, both namespace World and namespace World::Buildings are common ancestors of names-pace World::Buildings::Municipal and namespace World::Buildings::Business, but namespace World::Buildings is the LCA.

You should now be comfortable with the idea of a DAG and an LCA, so let’s examine exactly what a using-directive does. This is covered over three pages of the standard at [namespace.udir].2 To summarize, a using-directive introduces its new meanings into the scope which is the LCA of the current scope and the target namespace’s own scope. This can be quite surprising and becomes more surprising as a codebase grows and acquires more namespaces. Let’s try out a real-world exam-ple using the above namespace hierarchy.

2. https://eel.is/c++draft/namespace.udir

namespace Result {
  struct Victory {};
  struct DecisiveVictory : Victory {};
  struct CrushingVictory : DecisiveVictory {};
}

namespace gdg {
  int signal_result(Result::CrushingVictory);
}
namespace World {
  int signal_result(Result::CrushingVictory);
  namespace Buildings {
    namespace Municipal {
      int signal_result(Result::DecisiveVictory);
    }
    namespace Business {
      int compete() {
        using namespace Municipal;
        using namespace gdg;
        return signal_result(Result::CrushingVictory());

      }
    }
  }
}

We have added a Victory type in a new namespace called Result, and a function called signal_result that describes an outcome to a game. Let’s see what happens in the compete function.

First of all, using namespace Municipal injects int signal_result (Result::DecisiveVictory) into the LCA of Municipal and Business. Remember that this hides previous meanings of signal_result. Can you identify what this has hidden?

It has hidden gdg::signal_result and World::signal_result. Keep that in mind.

The next using-directive introduces gdg::signal_result into the global namespace. This is the LCA of namespace gdg and namespace Business. However, that itself has been hidden by World::signal_result, which has itself been hidden by Municipal::signal_result. So which signal_result is invoked at the end of the test function?

Municipal::signal_result is the only available choice. Even though it takes a DecisiveVictory rather than a CrushingVictory, it is the best fit, being the only fit.

Did you follow that, answer the questions correctly and deduce the correct function?

Now, I appreciate that this might seem contrived and overly complicated. How-ever, this is not too dissimilar from a real-world example that utterly defeated me for the best part of a week. I have taken out all the extraneous furniture that was nothing to do with the bug to aid exposition. Imagine this code spread over a few thousand lines through a handful of source files. Imagine this code manifesting by silently recording the wrong kind of victory that was not too far from the right kind of victory. Imagine thinking that this bug is unrelated to the quality of the victory, but to the way that the details of the victory are passed to the rest of the system. Why on earth would you think that CrushingVictory would not be recorded? There it is, right there in the code. The solution only came to me when I was stepping through the code and suddenly realized that I had gone to the wrong place. I blamed the debugger and spent far too long arguing about optimizers with anyone who would listen. Perhaps a COMDAT had been incorrectly folded. But no, the error was that I didn’t understand how using-directives worked.

An altogether more insidious problem

Perhaps you remain unconvinced. Perhaps you work on small projects and you do not nest your namespaces. There is yet another trap you can fall into. Consider this snippet of code from 2005:

// numerical_maths.h
namespace maths {
  float clamp(float v, float max, float min);
}
using namespace maths;

// results.cpp
#include "numerical_maths.h"
#include <algorithm>
using namespace std;

results.cpp contained quite a lot of calls like this:

int x = clamp(12, 6, 4);

Even though there was no overload that took int rather than float, the existing func-tion was a suitable candidate since int can convert to float, albeit with loss of preci-sion at higher magnitudes. Technically, this was a bug, but it failed to manifest in any code since the values were always reasonably small.

Something important happened in 2017: the function clamp was added to the std namespace. As a result, results.cpp contained an additional overload for clamp. Unfortunately, being a function template, it was a superior choice to maths::clamp() if the parameters were all of type int, since it required no parameter conversions. Fortunately, it did the same thing as maths::clamp(). Unfortunately, it took the max and min parameters in the reverse order. Fortunately, the implementation would warn you if the max parameter was less than the min parameter. Unfortunately, we disabled all the furniture that emitted this warning because it made debugging awfully slow.

As a result, weird and subtle bugs started appearing. Incorrect clamping results would propagate to unexpected parts of the code that were not necessarily cata-strophic but did not properly match with the intention. Nor was this limited to results.cpp: clamp was a widely used function. Fortunately, there was plenty of time to fix all the bugs and a huge sigh of relief was heaved by all at the end of that debacle.

Actually, that last sentence is not true.

It was not a happy time, and it wasn’t until someone was browsing through cppreference.com that they learned of the introduction of std::clamp, noticed that the order of parameters was different, and felt a terrible realization swamping their attention. With several thousand uses of clamp scattered through the codebase, a frustrating, detailed search through the codebase was required to ensure that the intended clamp was being called, and that the engineer had got their parameters the right way round.

Simply moving the using-directive out of the header file and into each source file where it was included would not have been enough: the same problem would have arisen, although at least the presence of a namespace would have advertised another source of symbols. Nor would making a using-declaration at the top of each source file have been sufficient either, since there were some places where the engineer had written code subsequent to the C++17 upgrade, inadvertently inverted the param-eters, but invoked the std::clamp function rather than the maths::clamp function. Each of those uses would have been invalidated.

The only defenses against this horror in the first instance were to make a using-declaration, not a using-directive, in the source file, ideally at the narrowest scope, or to fully qualify uses of clamp. When you put a using-directive in your source, you are exposing yourself to the risk of the silent introduction of new meanings to your sym-bols in other namespaces at inopportune moments. If you are lucky, you will get an “ambiguous symbol” compiler error. At worst, a superior overload will appear with subtle differences to the choice you were expecting.

We invite you to review the first part of this chapter.

Solving the problem of cluttered scope resolution operators

The convenience of using-directives at global scope is very hard to let go of. Without it, we have to liberally sprinkle scope resolution operators, ::, throughout our code. This is perhaps bearable when the namespace is std. But when the namespace is World::Buildings::Business, things become rather harder to read and easily appre-hend. Let’s review the correct use of namespaces.

Recall the existence of one single global namespace. The things you should declare in the global namespace are strictly limited to other namespaces, the main function, and extern "C" declarations. As a result of this, you should declare all your symbols inside your own namespaces.

Namespaces encapsulate symbols but also offer abstraction capabilities. Keep-ing abstractions small enough to apprehend in a single glance is a commendable strategy: gathering related classes into a single namespace is a form of abstraction. However, as projects grow the number of classes will grow, as will perforce the num-ber of namespaces if you are being smart about small abstractions. This leads to declarations as described above, such as World::Buildings::Business. This is a lot of typing and highlights why engineers are tempted by using-directives.

Fortunately, a more fine-grained approach exists. Aliasing is available to namespaces as well. This enables you to abbreviate World::Buildings::Business to something a little more palatable:

namespace BizBuild = World::Buildings::Business;

This namespace alias does not import any symbols into the current scope, or into a parent scope, or anything inconvenient and confusing like that. It simply introduces another name that aliases a namespace. From the above declaration of this names-pace, it allows you to type

auto x = BizBuild::compete();

rather than

auto x = World::Buildings::Business::compete();

Before namespaces, there was a common pattern of naming which sought to embed domains into identifiers, for example:

int BizBuild_compete();

which is the compete function within the Business Buildings domain. It is still com-mon to see engineers choosing to use underbars to separate domains from contextual identifiers, but there is no need if you have the facilities of namespaces and aliases. For the price of an extra character, a double colon rather than a single underbar, you get semantic separation of domain from identifier, of context from symbol. That semantic separation is visible to a C++ parser and becomes useful to editor tooling, such as automatic symbol completion, listing of namespace members, and so on.

The temptation and The Fall

We must offer one final warning. It is very hard to turn back from this path once you start to walk it.

Large codebases ideally consist of many libraries, each declaring their own namespace. It conveniently divides the solution domain; it is a useful abstraction technique. Each library will have a set of headers to expose its functionality through a well-designed API. If you decide to follow these declarations with a using-directive, clients of the code do not need to qualify any of the symbols. You may think you are doing them a favor by saving them extra typing.

This may seem like a good idea, but as the codebase increases in size and the num-ber of symbols starts to balloon, the chances of inadvertently creating overload sets increase. Eventually common sense will prevail, and you will have to remove all your using-directives. Suddenly your compilations will fail hard and heavy, as thousands of unknown symbols announce themselves to the compiler. The only solution will be to migrate the using-directives to clients of the header files, and then further out to the implementation files, and then finally to replace them with more specific using-declarations. This will not be a pleasant, rewarding, or especially educational task.

One day, tooling will solve this problem for us. We will be able to view source files and switch full namespace qualification of symbols on and off at the flick of a UI option. Warnings will be emitted when using-directives are encountered at the broadest scope, and suitable alternatives will be suggested that can be used to modify errant source code. Until then, please, please, follow the advice of Core Guideline SF.7: “Don’t write using namespace at global scope in a header file.”

Summary

In summary:

  • Using-directives at broad scopes are risky and costly, creating unexpected overload sets. Prefer using-declarations.

  • Ignoring this guideline becomes more dangerous as codebases grow, and more expensive to repair.

  • Make using-declarations at the narrowest comfortable scope, for example in class and function definitions.