using Directives by Example

A deep understanding of the features of a programming language is key in order to become a good programmer. With that philosophy in mind, lets take a closer look at using directives in C++.

Last time we said that:

A using directive works by placing everything belonging to the referred namespace inside the closest namespace shared by the referred namespace and the current scope.

The Referred Namespaces and the Current Scope

In order to better understand what that sentence means, lets take a look at what the current scope and the referred namespace are. Consider the following code:


int main() {
  using namespace std;
}

This code doesn’t do anything, but it will serve well for our purpose. In the using directive, the referred namespace is std. This is the namespace that we want to use. Note that you can use more than one using directive to refer different namespaces. When the main function executes, we enter into the scope of the main function, and that becomes the current scope. This means that the current scope is the scope of the block where the using directive is.

Now, you should be able to understand a little bit better what I meant. However, I’m going to go the extra mile here and show some examples that, both, demonstrate that the statement is true, and clarify the statement even further.

The Examples

Consider the following code:


#include 
int number = 0;

namespace outer {
  int number = 1;
  namespace inner_one {
    int number = 2;
  }
  namespace inner_two {
    int number = 3;
  }
}

int main() {
  return 0;
}

Here we have a namespace outer which contains a variable declaration, and two more namespaces. Each of these nested namespaces contains a variable declaration as well. There is also a variable defined in the global scope, outside of any namespace. All variables share the same name, but since they are in different scope, and more importantly, in different namespaces, they are all different entities. This example doesn’t do anything, and if you compile this program and run it, it won’t do anything. This is just our basic code. We will use this for all of our examples.

Lets first use a using directive that applies to the whole document:


#include 
int number = 0;

namespace outer {
  int number = 1;
  namespace inner_one {
    int number = 2;
  }
  namespace inner_two {
    int number = 3;
  }
}
using namespace outer;

int main() {
  std::cout << number;
  return 0;
}

Notice we’ve also added a cout statement. We are using the qualified name of cout so that we don’t have to use a using directive. I’m doing this so that the examples are not confusing.

If you try to compile that code you will get an error. Depending on the compiler you are using, the error may be different. In the g++ compiler, the error says “reference to ‘number’ is ambiguous”, and then it gives the line number where the two candidates are: 2, and 5. It also gives me the fully qualified names of the two candidates: int number, and int outer::number.

What does it mean anyway? The message is basically telling us that there are two different number variables, and that there is no way to know which one you mean. At first sight you would think that this happens because we have the using directive in the global scope, so when the members of outer are “imported” to be used without specifying their fully qualified name, they come to the global scope, and that is where the conflict happens. Following this logic, we would think that placing the using directive in the main function would solve the problem, since the members of the referred namespace would be placed in the scope of the main function, thus preventing the variables from conflicting with each other:


#include 
int number = 0;

namespace outer {
  int number = 1;
  namespace inner_one {
    int number = 2;
  }
  namespace inner_two {
    int number = 3;
  }
}

int main() {
  using namespace outer;
  std::cout << number;
  return 0;
}

If you do this, you may be surprised when you try to compile the program to see the same error you were seeing before. It made no difference. This means, then, that the members of the referred namespaces are not being placed in the current scope, but if they don’t go into the current scope, where do they go? As I said before, they go into the namespace share between the referred namespace and the current scope.

The Global Namespace

Lets take a break from the examples, and talk about the global namespace. Everything in C++ is in a namespace, even if you don’t explicitly put it there. If you don’t specify a namespace, things go into the global namepsace, which is referred to as ::. This means that the fully qualified name of the variable defined in the global scope (int number = 0) is ::number.

In our previous example, the closest shared namespace between the referred namespace and the current scope is the global namespace, so all the members of the referred namespace go into the global namespace, but in the global namespace there is already a variable named number, thus the conflict of ambiguity.

But, how do we know that everything in fact goes into the closest namespace rather than the global namepsace all the time? We can prove that quite easily:


#include 
int number = 0;

namespace outer {
  int number = 1;
  namespace inner_one {
    int number = 2;
  }
  namespace inner_two {
    int func() {
      using namespace inner_one;
      return number;
    }
  }
}

int main() {
  std::cout << outer::inner_two::func();
  return 0;
}

Here we have created a function in inner_two. We also removed the variable declaration, otherwise name lookup would find that variable first, and use it without giving us a chance to get to the ambiguous definitions.

If you try to compile this program you will see the same ambiguity problem, but this time the candidates are different: outer::number and outer::inner_one::number. This effectively demonstrates that the members of the referred namespace were placed in outer, which is the closest share namespace, rather than in the global space.

Getting Real

Why do conceptual things like this matter? Well, it all comes down to understanding of the language. The better you understand a language, the better prepared you are to write programs that have lesser chances of doing unexpected things. There are real life problems that derive from a poor understanding of the features of a language, and these are some of the worst kind of problems, because if you don’t understand how something works, you could be thinking that it works in a different way, and this will make it really hard to fix bugs because you cannot see why something is not working.

This is not just trivia knowledge. In fact, chances are no one will ever ask you to explain what a using directive does or how it works. This is knowledge that will prevent you from making mistakes. Knowing where to put something, like a using directive or a variable declaration within your code is essential in order to create good programs.

Next time, we will take a look at using declarations, how they work, and why you may want to use them in favor of using directive in some instances.