Search code examples
c++scopenamespacesargument-dependent-lookupusing-declaration

Why can an (irrelevant) using declaration reconcile overload ambiguity with Argument-Dependent Lookup?


This is a follow up of the question here on function overload with Argument-Dependent Lookup (ADL). I wanted to check my understanding of the rules under these circumstances so I wrote some test code.

First, there is no swap for HasPtr class in std, of course, so I wrote a namespace of my own which contains a HasPtr version of swap in addition to the one already defined in global scope. The using declaration works as what I expected -- an error of ambiguity was produced because there is a HasPtr version of swap already defined as does in "C++ Primer", 5ed.

Then I want to see what will happen if I change using declaration to using directive. The book says the compiler will keep silent until the function is actually called. I wanna verify that so the code is as follows:

#include <string>

class HasPtr {
public:
    friend void swap(HasPtr&, HasPtr&);
    std::string *ps;
};

void swap(HasPtr &lhs, HasPtr &rhs) {
    swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
}

namespace myNS {
    void swap(HasPtr &lhs, HasPtr &rhs)     {
        std::string s = "in my name space";
        swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
    }
}

class Foo {
    friend void swap(Foo &lhs, Foo &rhs);
    HasPtr h;
};


void swap(Foo &lhs, Foo &rhs) {
    using std::swap;  //<- commenting this line will cause error
    using namespace myNS;
    swap(lhs.h, rhs.h);
}

int main() {
    Foo f1, f2;
    swap(f1, f2);
}

Strange thing happened in line 27 (using std::swap;). If I commented it out, the name myNS::swap which has exactly the same signature as the one already defined in global scope was lifted to global scope and consequently causes an error of overloading ambiguity, as I expected.

But, if I do not comment line 27 and compile, no error of ambiguity is reported. And the program executes the ::swap originally defined in global scope as if the using directive using namespace myNS; does not lift myNS::swap so that it is not added to the candidate set for overloading. I just couldn't understand this phenomenon. Why can a using declaration from an irrelevant namespace (std certainly does not contain a HasPtr version of swap) reconcile overload ambiguity under ADL? Why is it the original ::swap, instead of its rival in myNS, that is selected to execute? Does the line 27 have any side effects to overloading process (say, suppressing names from lifted namespace so original names have a higher priority)? Thank you for your answers.

The problem can be reproduced in Visual Studio 2015 Update 3 on Windows 7 and in GCC 4.8.4 on ubuntu 14.04, both 64-bit.


Solution

  • The mechanics at play here are three fold.

    1. A using declaration, like using std::swap, is a declaration. It introduces a declaration of swap into the declarative region of the function.

    2. A using directive, on the other hand, does not introduce declarations into the current declarative region. It only allows unqualified lookup to treat names from the nominated namespaces, as though they were declared in the nearest enclosing namespace of the current declarative region.

    3. Declarations in smaller declarative regions hide declarations from the larger enclosing declarative regions.

    With respect to the above, the way you have it setup is like this:

    1. std::swap is declared inside swap(Foo, Foo).
    2. The names inside myNS are made available to swap(Foo, Foo), as though they were declared with it in the same namespace.
    3. The declaration added in #1, hides the one made visible in #2.
    4. ::swap can be found by ADL (despite also being hidden by #1), but myNS::swap can't. Since the myNS version is both hidden, and not found by ADL, it doesn't conflict with anything.

    When you remove the declaration of std::swap, now you have myNS::swap visible. ADL finds ::swap as well, giving you two overloads. They are both valid overloads, and produce an obvious ambiguity.