Edge Cases of Namespace Pollution

Summary

In the previous post, I introduced the rules of using directives (using namespace std) and using declarations (using std::vector). Generally speaking, the rules are good if there is no confliction, in which two using-declarations are the same.

As a quick recap,

  1. using directives are forbidden. Since this directive makes all names from a namespace available. It is a common cause of collisions and unexpected behavior. A using directive works since it adds another path into the lookup.
    // Forbidden -- This pollutes the namespace.
    using namespace foo;
  2. Use using-declarations instead. using-declarations only take the declared name into the current scope; lookup stops here.

In this post, I want to add more examples to show the behavior of namespace pollution in some edge cases.

Conclusion

Local using-declarations are better than global using-declarations. Type alias like Typedef or using mytype = int is even better.

#include <ostream>
//using namespace std; // NO!
//using ::std::cout;   // less bad than using namespace, it can be better if we scope it

using cw = console_gui::command_window;

void boo() {
    // console_gui::command_window::append("text")
    cw::append("text");
}

int main(int argc, char** argv) {
   int rc = do_some_stuff(argc, argv);
   using ::std::endl;
   if (rc) { // print the success report
      using ::std::cout;
      cout << "The test run completed. The return code was " << rc << '.' << endl;
    } else {
      using ::std::cerr;
      cerr << "Unable to complete the test run." << endl;
    }
    return 0 == rc;
}

Details

There are several edge cases.

Edge Case 0: The Call is Ambiguous

namespace T {
    void flunk(int) { std::cout << "T";}
}
namespace V {
    void flunk(int) { std::cout << "V";}
}
int main() {
    // 1.
    {
        using namespace V;
        using namespace T; 
        flunk(1);
    }
    // 2. 
    {
        using V::flunk;
        using T::flunk;
        flunk(1);
    }
}

The error happens at the compliation time (note that it is not multiple definition in the linking time, since the symbols are in different namespace).

main.cpp: In function ‘int main()’:
main.cpp: error: call of overloaded ‘flunk(int)’ is ambiguous
     flunk(1);

Edge Case 1: Specialization in Confliction

The global using declarations in this post will result in some weird behavior.

namespace mynamespace {
    int max(int, int) { return -1;}
}

using std::max;
using mynamespace::max;

int main() {
    printf("%d", max(1,2)); // it will print -1
}

Actually it kind-of has "confliction" in this case, it works since the compiler chooses the most specific one as a specialization of the template function max in std.

namespace mynamespace {
    template<typename T>
    T max(T i, T j) { return i;}
}
using std::max;
using mynamespace::max;
int main() {
    // 1. error: call of overloaded ‘max(int, int)’ is ambiguous
    printf("%d", max(1,2));
}

It will result in the same "Ambiguous" error in case 0.

Edge Case 2: Hide the Lookup

If a local variable has the same name as a namespace variable, the namespace variable is hidden, since it won’t do lookup when the symbol is available included by using-declarations.

namespace T {
    void flunk(int) { std::cout << "T" << std::endl; }
}
namespace V {
    void flunk(int) { std::cout << "V" << std::endl; }
}
int main() {
    {
        using T::flunk;
        using namespace V;
        flunk(1); // T
    }
    {
        using namespace V;
        using T::flunk;
        flunk(1); // T
    }
    {
        using V::flunk;
        using namespace T;
        flunk(1); // V
    }
    {
        using namespace T;
        using V::flunk;
        flunk(1); // V
    }
}

Leave a Reply

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