A Namespace in C++, informally, is a named scope that we can use to organize our code logically.
This will ensure that variables, functions, and classes with similar functionality are in the same scope. Not only that; it can also help avoid naming collisions since they are within a named scope.
Let’s look at how we can use namespaces in C++ to make our life easier.
What can a Namespace in C++ contain?
A namespace is just a declarative code block, which is bounded using a scope name (called the namespace name).
Since it is a code block, it can contain variables, constants, functions, and classes. But this is a declarative code block, so you can use this only to define all your variables and classes.
Let’s take an example. We’ll construct a namespace called MyNamespace
. We’ll leave it empty for now, and add stuff to it later.
1 2 |
namespace MyNamespace { } |
This is the syntax for declaring a namespace called MyNamespace
. We can insert all of our variables, etc, within this block.
So right now, our namespace is empty. If we want to make this useful for us, let’s add some members to it!
How can we access members of a namespace in C++?
I’ll declare a variable called my_data
, of type int, inside this namespace.
1 2 3 |
namespace MyNamespace { int my_data; } |
Whenever we declare something inside our namespace, we intend to use this from our main program later.
What happens if we try to modify my_data
by trying to access it directly?
1 2 3 4 5 6 7 8 |
namespace MyNamespace { int my_data; } int main() { // Try to access my_data directly will give a compilation error my_data = 100; return 0; } |
Output
1 2 3 4 5 6 7 |
test.cpp: In function ‘int main()’: test.cpp:8:14: error: ‘my_data’ was not declared in this scope my_data = 100; ^~~~~~~ test.cpp:8:14: note: suggested alternative: test.cpp:2:13: note: ‘MyNamespace::my_data’ int my_data; |
The compiler complains that it hasn’t seen a variable called my_data
in the global scope. How do we deal with this?
The answer lies in the hint that the compiler gives us!
We need to use the scope resolution operator (::
) so that our compiler can identify the scope.
Also, this brings me to the topic of a named scope. Since our namespace has a name, the corresponding scope name will be the namespace name!
So, we combine it with the scope resolution operator to get MyNamespace::my_data
! That’s it! Now, we can do all the things we desire, since we now have access to this variable.
Let’s rewrite our program to use the correct scope.
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> namespace MyNamespace { int my_data; } int main() { // Try to access my_data directly will give a compilation error MyNamespace::my_data = 100; std::cout << "Accessed MyNamespace::my_data! Assigning a value to it :" << MyNamespace::my_data << std::endl; return 0; } |
Output
1 |
Accessed MyNamespace::my_data! Assigning a value to it : 100 |
Now, we’ve fixed our problem! Let’s now go to another example to show another use case of namespaces – avoiding name collision.
Avoiding name collision between functions
Since we mentioned that the scope of all members inside a namespace is defined by the namespace name, we can use this to avoid renaming functions.
For example, in our namespace, assume that we define a function called add(x, y)
to add two numbers.
In our global scope, we can still have another separate function called add(x, y)
, or even add(x, y, z)
! This is because the scopes are different. We don’t need to unnecessarily rename functions now!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <iostream> // Example of a Namespace: // This is used to manage function signatures so that they don't conflict namespace MyNamespace { template <typename T, typename R> auto add(T x, R y) -> decltype(x + y) { return x + y; } } // Another function called add(mul, x, y), which multiplies the result of (x + y) with mul template <typename T, typename R> auto add(int mul, T x, R y) -> decltype(mul * (x + y)) { return mul * (x + y); } // The Main function int main() { std::cout << "Using MyNamespace::add() to add two types:n"; double x = 100.5, y = 200.5; std::cout << "Adding " << x << " and " << y << " to give " << MyNamespace::add(x, y) << std::endl; // Adding two different types, but type compatible with addition int a = 100; char c="A"; std::cout << "Adding " << a << " and " << c << " to give " << MyNamespace::add(a, c) << std::endl; std::cout << "Using add(mul, x, y) to add two types:n"; std::cout << "Adding " << a << " and " << c << " to give " << add(10, a, c) << std::endl; return 0; } |
I am using modern C++ practices in this example. Some of them are based on the Standard Template Library (STL). I am also using trailing return types, where I can specify the return type of a function after I declare its prototype.
Output
1 2 3 4 5 |
Using MyNamespace::add() to add two types: Adding 100.5 and 200.5 to give 301 Adding 100 and A to give 165 Using add(mul, x, y) to add two types: Adding 100 and A to give 1650 |
As you can see, we can indeed write two different functions add()
on the different scopes.
Let’s now go to another example of namespaces, using Classes.
Using Classes inside Namespaces
We can also use classes in our namespace. The same declaration pattern follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
namespace MyNamespace { // We can have functions inside a namespace template <typename T, typename R> auto add(T x, R y) -> decltype(x + y) { return x + y; } // We can also have a Class inside a namespace class MyClass { public: int a; MyClass(int val=0) { a = val; } ~MyClass() { std::cout << "Destructor called for MyNamespace::MyClassn"; } }; } |
Let’s write our driver program to utilize the new namespace members!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> // Example of a Namespace: // This is used to manage function signatures so that they don't conflict // Think of a namespace as a named scope binding various functions and classes namespace MyNamespace { // We can have functions inside a namespace template <typename T, typename R> auto add(T x, R y) -> decltype(x + y) { return x + y; } // We can also have a Class inside a namespace class MyClass { public: int a; MyClass(int val=0) { a = val; } ~MyClass() { std::cout << "Destructor called for MyNamespace::MyClassn"; } }; } // The Main function int main() { // Use MyClass from MyNamespace MyNamespace::MyClass my_obj(100); std::cout << "Created an object from MyNamespace::MyClassn"; std::cout << "my_obj.a = " << my_obj.a << std::endl; return 0; } |
Output
1 2 3 |
Created an object from MyNamespace::MyClass my_obj.a = 100 Destructor called for MyNamespace::MyClass |
We can also rewrite our example, by defining our Class outside the namespace, but using our scope resolution operator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
namespace MyNamespace { // We can have functions inside a namespace template <typename T, typename R> auto add(T x, R y) -> decltype(x + y) { return x + y; } // We can also have a Class inside a namespace class MyClass { public: int a; MyClass(int val); ~MyClass(); }; // We can define methods of the class within the namespace, but outside the class MyClass::MyClass(int val = 0) { MyClass::a = val; } } // We can also define it outside the namespace! MyNamespace::MyClass::~MyClass() { std::cout << "Destructor called for MyNamespace::MyClassn"; } |
Accessing members of a namespace in C++ directly
If the namespace dependency becomes long, it may be tedious and time consuming to type out the full scope of the member to access it. To overcome this, C++ introduced the using
directive.
This will allow us to access all the names (members) in a namespace directly!
We generally place this directive at the top of a file, so that it applies everywhere in the file.
To use this, the syntax is as follows:
1 |
using namespace namespace_name; |
Where namespace_name
is your namespace name. In my case, it is MyNamespace
. So, this statement will become:
1 |
using namespace MyNamespace; |
Now, we can directly access members inside of MyNamespace
! But keep in mind that if there is a global variable of the same name, the compiler will throw an error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> // Example of a Namespace: // This is used to manage function signatures so that they don't conflict namespace MyNamespace { int my_data; } using namespace MyNamespace; // The Main function int main() { // Can now access directly, provided there is no global variable of the same name! my_data = 200; std::cout << "my_data is now :" << my_data << std::endl; return 0; } |
Output
1 |
my_data is now :200 |
Conclusion
In this article, we learned about the concept of a namespace in C++, and how we can use them to logically organize our code. We also saw how we can use it to avoid name collisions, and also examples showing the using
directive.