3

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.

user5280911
  • 723
  • 1
  • 8
  • 21
  • *"was lifted to global scope"* - Not it wasn't. The closer analogy is that all of its names were dumped into the visibility pull of `void swap(Foo &lhs, Foo &rhs)`. – StoryTeller - Unslander Monica Oct 17 '17 at 07:51
  • Hi, StoryTeller, Thank you for the comment. What did you mean by "analogy" and "pull"? I can't catch it. – user5280911 Oct 17 '17 at 08:00
  • I mean *pool*, since I'm lame. – StoryTeller - Unslander Monica Oct 17 '17 at 08:00
  • The book says "A using directive ... has the effect of lifting the namespace members into the nearest scope that contains both the namespace itself and the using directive." I guess the "nearest scope" is the "closer analogy" you mentioned. But this should be the global scope. Another reason for my claim is that there would be no error of ambiguity (between ::swap and myNS::swap) if myNS::swap was not lifted to global scope when I commented out line 27. – user5280911 Oct 17 '17 at 08:04
  • The book isn't perfect. For all of its value, sometimes I find the way it chooses to phrase things unfortunate. – StoryTeller - Unslander Monica Oct 17 '17 at 08:14

2 Answers2

2

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Thank you all. PS: "as though" in 2 is important. It is not actual declaration from myNS. If we rewrite it as an inline namespace which produces actual declaration, even `using std::swap' won't work because (I guess) the compiler would report an ambiguity error right away before swap(Foo, Foo). – user5280911 Oct 17 '17 at 08:55
  • @user5280911 - Exactly. The rules are somewhat involved, and this is why I was in a twist over the Primer's choice of words to describe it. – StoryTeller - Unslander Monica Oct 17 '17 at 08:56
1

Note that using-directive and using-declaration have different effect:

(emphasis mine)

using-directive: 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 ns_name is visible as if it were declared in the nearest enclosing namespace which contains both the using-directive and ns_name.

That means for using namespace myNS;, the name myNS::swap is visible as if it were declared in the global scope. If using std::swap; is commented, then 2 swaps will be found at the global scope and then cause the ambiguity.

If using std::swap; is uncommented, the swap from std namespace will be found at the function scope, then the name lookup stops, the further global scope won't be checked. Note that the global ::swap could be found by ADL, with the addition of std::swap they will be considered in overload resolution, and ::swap is selected then no ambiguity. myNS::swap won't kick in for this case.

name lookup examines the scopes as described below, until it finds at least one declaration of any kind, at which time the lookup stops and no further scopes are examined.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405