C++ For C# Developers: Part 17 – Namespaces
With structs wrapped up, we can move on to other features of C++. Today we’ll take a look at namespaces. We’ll cover the basics that C# provides, but go so much further and cover a lot of advanced functionality. Read on to learn all about them!
Table of Contents
- Part 1: Introduction
- Part 2: Primitive Types and Literals
- Part 3: Variables and Initialization
- Part 4: Functions
- Part 5: Build Model
- Part 6: Control Flow
- Part 7: Pointers, Arrays, and Strings
- Part 8: References
- Part 9: Enumerations
- Part 10: Struct Basics
- Part 11: Struct Functions
- Part 12: Constructors and Destructors
- Part 13: Initialization
- Part 14: Inheritance
- Part 15: Struct and Class Permissions
- Part 16: Struct and Class Wrapup
- Part 17: Namespaces
- Part 18: Exceptions
- Part 19: Dynamic Allocation
- Part 20: Implicit Type Conversion
- Part 21: Casting and RTTI
- Part 22: Lambdas
- Part 23: Compile-Time Programming
- Part 24: Preprocessor
- Part 25: Intro to Templates
- Part 26: Template Parameters
- Part 27: Template Deduction and Specialization
- Part 28: Variadic Templates
- Part 29: Template Constraints
- Part 30: Type Aliases
- Part 31: Deconstructing and Attributes
- Part 32: Thread-Local Storage and Volatile
- Part 33: Alignment, Assembly, and Language Linkage
- Part 34: Fold Expressions and Elaborated Type Specifiers
- Part 35: Modules, The New Build Model
- Part 36: Coroutines
- Part 37: Missing Language Features
- Part 38: C Standard Library
- Part 39: Language Support Library
- Part 40: Utilities Library
- Part 41: System Integration Library
- Part 42: Numbers Library
- Part 43: Threading Library
- Part 44: Strings Library
- Part 45: Array Containers Library
- Part 46: Other Containers Library
- Part 47: Containers Library Wrapup
- Part 48: Algorithms Library
- Part 49: Ranges and Parallel Algorithms
- Part 50: I/O Library
- Part 51: Missing Library Features
- Part 52: Idioms and Best Practices
- Part 53: Conclusion
Namespace Basics
Namespaces serve the same high-level purpose in C++ as they do in C#. They allow us to reuse identifiers and disambiguate them by namespace. The basic syntax even looks the same:
namespace Math { struct Vector2 { float X; float Y; }; }
Also like C#, we can reopen the namespace to add on to it by simply reusing the name:
namespace Math { struct Vector2 { float X; float Y; }; } namespace Math { struct Vector3 { float X; float Y; float Z; }; }
We can also nest namespaces:
namespace Math { namespace LinearAlgebra { struct Vector2 { float X; float Y; }; } }
Accessing members of the namespace is a bit different. As we’ve seen with enums and structs, we continue to use the scope resolution operator (A::B
) instead of C#’s dot syntax (A.B
).
Math::Vector2 vec{2, 4}; // Refer to Vector2 in the Math namespace DebugLog(vec.X, vec.Y); // 2, 4
To refer to the namespace implicitly created for the global scope, we use ::B
instead of global::B
as in C#:
int32_t highScore = 0; class Player { int32_t numPoints; int32_t highScore; void ScorePoints(int32_t num) { numPoints += num; // highScore refers to the data member if (numPoints > highScore) { highScore = numPoints; } // ::highScore refers to the global variable if (numPoints > ::highScore) { ::highScore = numPoints; } } };
As of C++17, we can also use the scope resolution operator to create nested namespaces:
namespace Math::LinearAlgebra { struct Vector2 { float X; float Y; }; }
Unlike C#, we’re not limited to only putting types like structs and enums in a namespace. We can put anything we want there:
namespace Math { // Variable const float PI = 3.14f; // Function bool IsNearlyZero(float val, float threshold=0.0001f) { return abs(val) < threshold; } }
We can also put declarations inside the namespace and definitions outside:
namespace Math { // Declarations struct Vector2; bool IsNearlyZero(float val, float threshold=0.0001f); } // Definitions struct Math::Vector2 { float X; float Y; }; bool Math::IsNearlyZero(float val, float threshold) { return abs(val) < threshold; }
The definitions need to be in either an enclosing namespace or at global scope:
namespace Math { // Declarations struct Vector2; bool IsNearlyZero(float val, float threshold=0.0001f); } // Definitions namespace Other { // Compiler error: Other isn't an enclosing namespace or global scope struct Math::Vector2 { float X; float Y; }; // Compiler error: Other isn't an enclosing namespace or global scope bool Math::IsNearlyZero(float val, float threshold) { return abs(val) < threshold; } }
Note that a namespace with only functions is another way to mimic a C# static class
.
Using Directives
Explicitly writing out namespace names like Math::
gets tedious and verbose. As in C#, C++ has using
directives to alleviate this issue. The syntax looks similar:
// Using directive using namespace Math; // No need for Math:: Vector2 vec{2, 4}; DebugLog(vec.X, vec.Y); // 2, 4
Unlike C# where using
directives have to be at the top of a file, C++ allows them anywhere in the global scope, in a namespace, or even in any block:
namespace MathUtils { // Using directive inside a namespace using namespace Math; bool IsNearlyZero(Vector2 vec, float threshold=0.0001f) { return abs(vec.X) < threshold && abs(vec.Y) < threshold; } } void Foo() { // Using directive inside a function using namespace Math; // No need for Math:: Vector2 vec{2, 4}; DebugLog(vec.X, vec.Y); // 2, 4 } enum struct Op { IS_NEARLY_ZERO }; bool DoOp(Math::Vector2 vec, Op op) { if (op == Op::IS_NEARLY_ZERO) { // Using directive inside a block using namespace MathUtils; return IsNearlyZero(vec); } return false; }
Also unlike C#, using
directives are transitive. In the above, the MathUtils
namespace has using namespace Math
. That means any using namespace MathUtils
implicitly includes a using namespace Math
:
// Implicitly includes MathUtils' "using namespace Math" using namespace MathUtils; // No need for Math:: due to transitive using directive Vector2 vec{2, 4}; // No need for MathUtils:: DebugLog(IsNearlyZero(vec)); // false
Even members added to the namespace after the using
directive are included transitively:
namespace Math { struct Vector2 { float X; float Y; }; } namespace MathUtils { using namespace Math; bool IsNearlyZero(Vector2 vec, float threshold=0.0001f) { return abs(vec.X) < threshold && abs(vec.Y) < threshold; } } namespace Math { struct Vector3 { float X; float Y; float Z; }; } void Foo() { // Implicitly includes MathUtils' "using namespace Math" // Includes Vector3, even though it was after "using namespace Math" using namespace MathUtils; Vector3 vec{2, 4, 6}; DebugLog(vec.X, vec.Y, vec.Z); // 2, 4, 6 }
Note that it is generally considered a bad practice to place using
directives in the global scope of header files as it imposes the entirety of the namespace as well as any transitively-used namespaces on all files that #include
it.
Inline Namespaces
C++ namespaces may be inline
:
inline namespace Math { struct Vector2 { float X; float Y; }; }
This is similar to a non-inline
namespace immediately followed by a using
directive:
namespace Math { struct Vector2 { float X; float Y; }; } using namespace Math;
We can therefore use the members of the namespace without the scope resolution operator or an explicit using
directive:
Vector2 vec{2, 4}; DebugLog(vec.X, vec.Y); // 2, 4
As of C++20, we can add the keyword inline
before each name except the first when defining nested namespaces:
// Math is a non-inline namespace // LinearAlgebra is an inline namespace nested in Math namespace Math::inline LinearAlgebra { struct Vector2 { float X; float Y; }; } // Math:: still required as Math is not an inline namespace Math::Vector2 vec{2, 4}; DebugLog(vec.X, vec.Y); // 2, 4
This adds convenience to a normal use case for inline
namespaces. It’s typical to want to group together functionality, but also offer it piecemeal. For example, the C++ Standard Library offers user-defined literals for the string
and hour
types like this:
namespace std::inline literals::inline string_literals { std::string operator""s(const char* chars, size_t len) { // ... implementation ... } } namespace std:inline literals::inline chrono_literals { std::chrono::hours operator""h(long double val) { // ... implementation ... } } void UseAllLiterals() { // Transitively use string_literals and chrono_literals // No need to specify each one using namespace std::literals; std::string greeting = "hello"s; std::chrono::hours halfHour = 0.5h; } void UseJustStringLiterals() { // Use just string_literals using namespace std::literals::string_literals; std::string greeting = "hello"s; std::chrono::hours halfHour = 0.5h; // Compiler error } void UseJustChronoLiterals() { // Use just chrono_literals using namespace std::literals::chrono_literals; std::string greeting = "hello"_s; // Compiler error std::chrono::hours halfHour = 0.5h; }
Unnamed Namespaces
Namespaces may have no name. Just like with inline
namespaces, these have an implicit using
directive right after them:
namespace { struct Vector2 { float X; float Y; }; } // Implicitly added by the compiler // UNNAMED is just a placeholder for the name the compiler gives the namespace using namespace UNNAMED; // Can use members of the unnamed namespace Vector2 vec{2, 4}; DebugLog(vec.X, vec.Y); // 2, 4
Because these namespaces have no name, there’s no way to explicitly refer to their members with the scope resolution operator (A::B
) or name them in a using
directive.
Specially, all members of unnamed namespaces, including nested namespaces, have internal linkage, just like static
global variables.
// other.cpp int32_t Global; // External linkage namespace { int32_t InNamespace; // Internal linkage } // test.cpp extern int32_t Global; // OK: has external linkage extern int32_t InNamespace; // Linker error: has internal linkage // Also, no way to name the namespace here void Foo() { Global = 123; InNamespace = 456; }
Using Declarations
Besides using
directives, C++ also has using
declarations:
namespace Math { struct Vector2 { float X; float Y; }; struct Vector3 { float X; float Y; float Z; }; } // Use just Vector2, not Vector3 using Math::Vector2; Vector2 vec2{2, 4}; // OK Vector3 vec3a{2, 4, 6}; // Compiler error Math::Vector3 vec3b{2, 4, 6}; // OK
Like variable declarations, we can name multiple namespace members in a single using
declaration:
namespace Game { class Player; class Enemy; } void Foo() { // Use Vector2 and Player, not Vector3 or Enemy using Math::Vector2, Game::Player; Vector2 vec2{2, 4}; // OK Vector3 vec3{2, 4}; // Compiler error Player* player; // OK Enemy* enemy; // Compiler error }
Unlike using
directives, using
declarations actually add the specified namespace members to the block they’re declared in. This means some conflicts are possible:
namespace Stats { int32_t score; } namespace Game { struct Player { int32_t Score; }; } bool HasHighScore(Game::Player* player) { using Stats::score; int32_t score = player->Score; // Compiler error: score already declared return score > score; // Ambiguous reference to score }
There’s one case where it’s OK to have more than one identifier with the same name: function overloads. With multiple using
declarations referring to functions with the same name, we can create an overload set within the function:
namespace Game { struct Player { int32_t Health; }; } namespace Damage { struct Weapon { int32_t Damage; }; void Use(Weapon& weapon, Game::Player& player) { player.Health -= weapon.Damage; } } namespace Healing { struct Potion { int32_t HealAmount; }; void Use(Potion& potion, Game::Player& player) { player.Health += potion.HealAmount; } } void DamageThenHeal( Game::Player& player, Damage::Weapon& weapon, Healing::Potion& potion) { using Damage::Use; // Now have one Use function using Healing::Use; // Now have two Use functions: an overload set Use(weapon, player); // Call the Use(Weapon&, Player&) overload Use(potion, player); // Call the Use(Potion&, Player&) overload } Game::Player player{100}; Damage::Weapon weapon{20}; Healing::Potion potion{10}; DamageThenHeal(player, weapon, potion); DebugLog(player.Health); // 90
Namespace Aliases
The final namespace feature is a simple one: aliases. We can use these to shorten long, usually nested namespace names:
namespace Math { namespace LinearAlgebra { struct Vector2 { float X; float Y; }; } } // mla is an alias for Math::LinearAlgebra namespace mla = Math::LinearAlgebra; mla::Vector2 vec2{2, 4}; DebugLog(vec2.X, vec2.Y); // 2, 4
This is pretty close to using N1 = N2
in C#. The major difference is that it can be placed in the global scope, in a namespace, or in any other block. It doesn’t need to be placed at the top of the file and only apply to that one file.
Conclusion
C++ namespaces are, in many ways, very similar to C# namespaces. As is typical, they form a rough superset of the C# functionality. Advanced features like inline
namespaces, using
declarations, and unnamed namespaces are available to us. We also have the ability to put variables and functions in them in addition to type definitions. We can even put using
declarations and directives nearly anywhere, not just at the top of files.
As is typical with more power though, more complexity is involved. We need to avoid overly-broad using
directives, pay attention to transitive using
directives, and resolve identifier conflicts with using
declarations.
Namespaces are used in nearly every C++ codebase, as they are in nearly every C# codebase. We now know the rules for how to use them, so we’re one step closer to effectively writing C++!
#1 by Jamie on September 10th, 2020 ·
Hi Jackson,
Great explanation of namespaces. Very easy to follow, you’re great at explaining complex topics. I’m learning quite a lot about C++ from your blog.
Take care
Jamie
#2 by bidjei on May 24th, 2022 ·
C# Usings also create extremely useful aliases for classes, so you don’t have to make a bunch of empty extensions. Aliasing is not just for naming collisions; aliasing does wonders for code readability.
Do this:
using Code = Microsoft.Collections.Generic.List
…
var x = new Code();
Not this:
public class Code : Microsoft.Collections.Generic.List {}
#3 by Andrew on June 26th, 2022 ·
Minor:
using Alias::Vector2 probably should be using Math::Vector2 to keep with the other examples in the following code block:
#4 by jackson on June 26th, 2022 ·
Thanks for another good catch!