Tip of the Week #119: Using-declarations and namespace aliases

Originally posted as totw/119 on 2016-07-14

By Thomas Köppe ([email protected])

This tip gives a simple, robust recipe for writing using-declarations and namespace aliases in .cc files that avoids subtle pitfalls.

Before we dive into the details, here is an example of the recipe in action:

namespace example {
namespace makers {
namespace {

using ::otherlib::BazBuilder;
using ::mylib::BarFactory;
namespace abc = ::applied::bitfiddling::concepts;

// Private helper code here.

}  // namespace

// Interface implementation code here.

}  // namespace makers
}  // namespace example

Remember that everything in this tip applies only to .cc files, since you should never put convenience aliases in header files. Such aliases are a convenience for the implementer (and the reader of an implementation), not an exported facility. (Names that are part of the exported API may of course be declared in headers.)

Summary

  • Never declare namespace aliases or convenience using-declarations at namespace scope in header files, only in `.cc` files.
  • Declare namespace aliases and using-declarations inside the innermost namespace, whether named or anonymous. (Do not add an anonymous namespace just for this purpose.)
  • When declaring namespace aliases and using-declarations, use fully qualified names (with leading `::`) unless you are referring to a name inside the current namespace.
  • For other uses of names, avoid fully qualifying when reasonable, see TotW 130.

(Remember that you can always have a local namespace alias or using-declaration in a block scope, which can be handy in header-only libraries.)

Background

C++ organizes names into namespaces. This crucial facility allows code bases to scale by keeping ownership of names local avoiding name collisions in other scopes. However, namespaces impose a certain cosmetic burden, since qualified names (foo::Bar) are often long and quickly become clutter. We often find it convenient to use unqualified names (Bar). Additionally, we may wish to introduce a namespace alias for a long but frequently used namespace: namespace eu = example::v1::util; We will collectively call using-declarations and namespace aliases just aliases in this tip.

The Problem

The purpose of a namespace is to help code authors avoid name collisions, both at the point of name lookup and at the point of linking. Aliases can potentially undermine the protection afforded by namespaces. The problem has two separate aspects: the scope of the alias, and the use of relative qualifiers.

Scope of the Alias

The scope at which you place an alias can have subtle effects on code maintainability. Consider the following two variants:

using ::foo::Quz;

namespace example {
namespace util {

using ::foo::Bar;

It appears that both using-declarations are effective at making the names Bar and Quz available for unqualified lookup inside our working namespace ::example::util. For Bar, everything is working as expected, provided there is no other declaration of Bar inside namespace ::example::util. But this is your namespace, so it is in your power to control this.

On the other hand, if a header is later included that declares a global name Quz, then the first using-declaration becomes ill-formed, as it attempts to redeclare the name Quz. And if another header declares ::example::Quz or ::example::util::Quz, then unqualified lookup will find that name rather than your alias.

This brittleness can be avoided if you do not add names to namespaces that you do not own (which includes the global namespace). By placing the aliases inside your own namespace, the unqualified lookup finds your alias first and never continues to search containing namespaces.

More generally, the closer a declaration is to the point of use, the smaller is the set of scopes that can break your code. At the worst end of our example is Quz, which can be broken by anyone; Bar can only be broken by other code in ::example::util, and a name that is declared and used inside an unnamed namespace cannot be broken by any other scope. See Unnamed Namespaces for an example.

Relative Qualifiers

A using-declaration of the form using foo::Bar seems innocuous, but it is in fact ambiguous. The problem is that it is safe to rely on the existence of names in a namespace, but it is not safe to rely on the absence of names. Consider this code:

namespace example {
namespace util {

using foo::Bar;

It is perhaps the author’s intention to use the name ::foo::Bar. However, this can break, because the code is relying on the existence of ::foo::Bar and also on the non-existence of namespaces ::example::foo and ::example::util::foo. This brittleness can be avoided by qualifying the used name fully: using ::foo::Bar.

The only time that a relative name is unambiguous and cannot possibly be broken by outside declarations is if it refers to a name that is already inside your current namespace:

namespace example {
namespace util {
namespace internal {

struct Params { /* ... */ };

}  // namespace internal

using internal::Params;  // OK, same as ::example::util::internal::Params

This follows the same logic that we discussed in the previous section.

What if a name lives in a sibling namespace, such as ::example::tools::Thing? You can say either tools::Thing or ::example::tools::Thing. The fully qualified name is always correct, but it may also be appropriate to use the relative name. Use your own judgment.

A cheap way to avoid a lot of these problems is not to use namespaces in your project that are the same as popular top-level namespaces (such as util); the Style Guide recommends this practice explicitly.

Demo

The following code shows examples of both failure modes.

helper.h:

namespace bar {
namespace foo {

// ...

}  // namespace foo
}  // namespace bar

some_feature.h:

extern int f;

Your code:

#include "helper.h"
#include "some_feature.h"

namespace foo {
void f();
}  // namespace foo

// Failure mode #1: Alias at a bad scope.
using foo::f;  // Error: redeclaration (because of "f" declared in some_feature.h)

namespace bar {

// Failure mode #2: Alias badly qualified.
using foo::f;  // Error: No "f" in namespace ::bar::foo (because that namespace was declared in helper.h)

// The recommended way, robust in the face of unrelated declarations:
using ::foo::f;  // OK

void UseCase() { f(); }

}  // namespace bar

Unnamed Namespaces

A using-declaration placed in an unnamed namespace can be accessed from the enclosing namespace and vice versa. If you already have an unnamed namespace at the top of the file, prefer putting all aliases there. From within that unnamed namespace, you gain an extra little bit of robustness against clashing with something declared in the enclosing namespace.

namespace example {
namespace util {

namespace {

// Put all using-declarations in here. Don't spread them over the file.
using ::foo::Bar;
using ::foo::Quz;

// In here, Bar and Quz refer inalienably to your aliases.

}  // namespace

// Can use both Bar and Quz here too. (But don't declare any entities called Bar or Quz yourself now.)

Non-aliased names

So far we have been talking about local aliases for distant names. But what if we want to use names directly and not create aliases at all? Should we say util::Status or ::util::Status?

There is no obvious answer. Unlike the alias declarations that we have been discussing until now, which appear at the top of the file far away from the actual code, the direct use of names affects the local readability of your code. While it is true that relatively qualified names may break in the future, there is a significant cost for using absolute qualifications. The visual clutter caused by the leading :: may well be distracting and not worth the added robustness. In this case, use your own judgment to decide which style you prefer. See TotW 130.

Acknowledgments

All credit is due to Roman Perepelitsa ([email protected]) who originally suggested this style in a mailing list discussion and who contributed numerous corrections and punchlines. However, all errors are mine.


Subscribe to the Abseil Blog