Using namespace considered harmful
Published on: 2025-01-10
A look at the problems using namespace can cause, and some alternatives to reach for instead.
By Tom Hulton-Harrop
Motivation
Something we strive for as developers is to ensure the code we write today will continue working long into the future. Code should not be brittle, it should not depend on something that’s true today, but may not be true tomorrow. Using-directives (statements such as using namespace std;
) are a deceptively simple part of C++ that can lay traps for us in future. If we’re lucky, this will manifest as a compile error, if not, some observable aspect of the program may change without our knowing.
Example
Let’s begin with a short snippet to highlight how using namespace
can cause problems.
namespace Testing { struct Expectation { Expectation() { printf("Testing:Expectation\n"); } };} // namespace Testing
namespace Production { namespace Example { void DoSomething() { using namespace Testing; Expectation expect; } } // namespace Example} // namespace Production
int main(int argc, char** argv) { Production::Example::DoSomething(); // prints Testing:Expectation}
Compiler Explorer link.
This short snippet compiles today, but what happens if in future someone decides to define a type called Expectation
at global scope.
... // as before
struct Expectation { Expectation() { printf("Expectation\n"); }};
namespace Production { namespace Example { void DoSomething() { using namespace Testing; Expectation expect; // Error: Expectation is ambiguous } } // namespace example} // namespace production
... // as before
Compiler Explorer link.
This code will fail to compile, as Expectation
is now ambiguous. This happens because the using-directive is not actually inserting names into the scope in which it is declared. The names are inserted into the nearest common ancestor of the mentioned namespace (Testing
) for the duration of its scope, which here is the global namespace.
Suppose the maintainer instead of attempting to add Expectation
to the global scope, added it to the same namespace our usage code is contained in. What will be printed?
... // as before
namespace Production { struct Expectation { Expectation() { printf("Production:Expectation\n"); } };} // namespace Production
... // as before
int main(int argc, char** argv) { Production::Example::DoSomething(); // prints Production:Expectation}
Compiler Explorer link
This is definitely surprising, and not what the original author would have anticipated or intended. This happens because Testing::Expectation
is effectively now at global scope. Name look-up rules will search in the enclosing namespace first, then the next namespace up, and so on, until the global scope is reached. As the using-directive has lifted Testing::Expectation
to the global scope, the compiler winds up finding Production::Expectation
first, and then terminates its search.
If we added an Expectation
type to the Example
namespace, it would be found first, and so we’d see "Production:Example:Expectation"
printed.
... // as before
namespace Production { struct Expectation { Expectation() { printf("Production:Expectation\n"); } }; namespace Example { struct Expectation { Expectation() { printf("Production:Example:Expectation\n"); } }; } // namespace Example} // namespace Production
... // as before
int main(int argc, char** argv) { Production::Example::DoSomething(); // prints Production:Example:Expectation}
Compiler Explorer link
In fact, with the above example, it becomes impossible to actually refer to Testing::Expectation
through use of a using-directive. This general problem only gets worse when dealing with multiple using-directives in a single function or file (every new using-directive could add quadratic risk of name collisions and build failures), and certain namespaces can contain a very large number of symbols that are all added to the global namespace.
Fortunately, there is a superior alternative to using-directives that avoids the issues discussed - using declarations and namespace aliases.
Using directives
Using directives allow a specific type to be introduced for the scope of the function.
// ... as before
namespace Production { namespace Example { void DoSomething() { using Testing::Expectation; Expectation expect; } } // namespace Example} // namespace Production
int main(int argc, char** argv) { Production::Example::DoSomething(); // prints Testing:Expectation}
Compiler Explorer link
One thing to be aware of, is if there happens to be a Testing
namespace nested inside Production
, that also contains a type called Expectation
, it will be found, not the one in the global Testing
namespace. To make sure this is the case, prefix the using
declaration with ::
to refer to the global scope.
namespace Testing { struct Expectation { Expectation() { printf("Testing:Expectation\n"); } };} // namespace Testing
namespace Production { namespace Testing { struct Expectation { Expectation() { printf("Production:Testing:Expectation\n"); } }; } // namespace testing
namespace Example { void DoSomething() { using ::Testing::Expectation; Expectation expect; } } // namespace Example} // namespace Production
int main(int argc, char** argv) { Production::Example::DoSomething(); // prints Testing:Expectation}
Compiler Explorer link
Another option is to shorten the full namespace name using a namespace alias. This can be done like so.
namespace Example { void DoSomething() { namespace Pt = ::Production::Testing; Pt::Expectation expect; }} // namespace Example
This should be used with care however, as it can obfuscate as much as it clarifies. It’s a good idea to keep the scope limited too (likely to a that of a function).
The last option is to simply use a the fully qualified name. This can often make code more readable and help signpost where code is coming from. It’s a little more typing, but it follows the rule of optimizing for the reader, not the writer. There are cases where a using declaration is required (e.g. When making use of ADL with swap - using std::swap;
), but otherwise always weigh up what benefit it’s really bringing to the code.
Deliberation
using namespace
is a C++ feature best avoided. It can lead to brittle code and subtle bugs. Better alternatives exist, and future maintainers will thank you for not making their lives more difficult. Definitely be on the lookout for them in PRs and politely suggest to the author they refrain from using them 🙂.
Further Reading
For an even deeper dive into this topic with more examples, please see this excellent blog post from the Google Abseil C++ Tips series.
Using declarations and namespace aliases aren’t without their pitfalls. There are two more excellent Abseil C++ Tips post worth reading on the subject.
- Tip of the Week #119: Using-declarations and namespace aliases
- Tip of the Week #130: Namespace Naming
Addendum
All of the above is made even more perilous with the inclusion of Unity builds (where multiple .cpp
files are #included
together to improve compilation times). This allows for using directives (and all other language features for that matter), to leak into other source files that a developer may not have anticipated. This is another excellent reason to avoid using namespace
.