c++namespacesc++17language-lawyerusing-directives

About the ambiguity of using a name vs using a namespace when doing unqualified calls


I knew that this would be ambiguous

#include <boost/hana/fwd/equal.hpp>
#include <range/v3/algorithm/equal.hpp>
#include <vector>

int main() {
    std::vector<int> v{1,2,3};
    using namespace boost::hana;
    using namespace ranges;
    equal(v, v);
}

because there's an equal both in boost::hana and ranges namespaces.

However, I thought this would be ambiguous too:

#include <boost/hana/fwd/equal.hpp>
#include <range/v3/algorithm/equal.hpp>
#include <vector>

int main() {
    std::vector<int> v{1,2,3};
    using namespace boost::hana;
    using ranges::equal;
    equal(v, v);
}

But that's not the case, according to GCC and Clang.

Why is that?


Solution

  • Snippet 2

    Lets look at how the second snippet in your example works since you already know the reason for why the first snippet produces ambiguous error.

    From Using directives' documentation:

    Using-directives are allowed only in namespace scope and in block scope. From the point of view of unqualified name lookup of any name after a using-directive and until the end of the scope in which it appears, every name from namespace-name is visible as if it were declared in the nearest enclosing namespace which contains both the using-directive and namespace-name.

    Using-directive does not add any names to the declarative region in which it appears (unlike the using-declaration), and thus does not prevent identical names from being declared.

    This means that a using directive does not introduce name in the declarative region (which is nothing but the main function in your example), but instead to the nearest enclosing namespace( which is the global namespace in your example).

    Now, when unqualified lookup happens for the call expression equal(v, v); the search goes upwards from the point where the call expression was encountered and it finds the equal version that was introduced due to the using declaration in the declarative region(which is main) and hence the search stops. And so this already found version is used.

    A contrived example might help clarify the situation:

    #include <iostream>
    namespace X 
    {
        void func(){std::cout<<"X version called"<<std::endl;}
    }
    namespace Y 
    {
        void func(){std::cout<<"Y version called"<<std::endl;};
    }
    int main()
    {
        using namespace X;
        using Y::func;
        
        func(); //calls Y version
        return 0;
    }
    

    Demo


    Snippet 1

    Snippet 1 in your example can also be understood based upon the quoted statement above. In particular, you're getting ambiguous error in snippet 1 because both the namespaces ranges and boost::hana have a function called equal that are equally ranked. So when the unqualified name lookup happens for the call expression equal(v,v) the search starts and goes upwards from the point where the call expression was encountered and finds functions named equal that were made visible in the global namescope due to both of the namespaces. Moreover, since both are equally ranked, we get the mentioned ambiguous error.

    A contrived example might help clarify the situation:

    #include <iostream>
    namespace X 
    {
        void func(int)
        {
            std::cout<<"X version func called"<<std::endl;
        }
        
        void foo()
        {
            std::cout<<"X version foo called"<<std::endl;
        }
    }
    namespace Y 
    {
        void func(double)
        {
            std::cout<<"Y version func called"<<std::endl;
        }
        void foo()
        {
            std::cout<<"Y version foo called"<<std::endl;
        }
    }
    int main()
    {
        using namespace X ;
        using namespace Y;
        
        func(3); //calls X's func 
        func(5.5);//calls Y's func
        
        foo();//fails due to ambiguity
        return 0;
    }
    

    Demo