Understanding Namespace in C++

Filed Under: C++
Namespace Cpp

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.

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.

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?

namespace MyNamespace {
    int my_data;
}

int main() {
    // Try to access my_data directly will give a compilation error
    my_data = 100;
    return 0;
}

Output

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.

#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

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!

#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

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.

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::MyClass\n"; }
    };
}

Let’s write our driver program to utilize the new namespace members!

#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::MyClass\n"; }
    };
}

// The Main function
int main() {
    // Use MyClass from MyNamespace    MyNamespace::MyClass 
    my_obj(100);
    std::cout << "Created an object from MyNamespace::MyClass\n";
    std::cout << "my_obj.a = " << my_obj.a << std::endl;
    return 0;
}

Output

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.

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::MyClass\n";
}

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:

using namespace namespace_name;

Where namespace_name is your namespace name. In my case, it is MyNamespace. So, this statement will become:

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.

#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

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.


References


Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages