3
#include <iostream>

struct H
{
    void swap(H &rhs); 
};    

void swap(H &, H &)
{
    std::cout << "swap(H &t1, H &t2)" << std::endl;
}

void H::swap(H &rhs)
{
    using std::swap;
    swap(*this, rhs);
}


int main(void)
{
    H a;
    H b;

    a.swap(b);
}

And this is the result:

swap(H &t1, H &t2)

In the code above, I try to define a swap function of H. In the function void H::swap(H &rhs), I use an using declaration to make the name std::swap visible. If there isn't an using declaration, the code cannot be compiled because there is no usable swap function with two parameters in class H.

I have a question here. In my opinion, after I used the using declaration -- using std::swap, it just make the std::swap -- the template function in STL visible. So I thought that the swap in STL should be invoked in H::swap(). But the result showed that the void swap(H &t1, H &t2) was invoked instead.

So here is my question:

  1. Why can't I invoke swap without a using declaration?(I guess it is because there is no swap function with two parameters in the class. But I am not sure. )
  2. Why will the swap of my definition be invoked instead of the STL swap in the H::swap?
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    You left a lot of irrelevant (distracting) code in your question. Provide a [MCVE] please. – πάντα ῥεῖ Mar 09 '16 at 14:58
  • 1
    I'd write an answer, but I'm not sure about the technicalities of argument-dependent lookup. It should be a pretty googlable term, though. (Your suspicion 1 is correct; overload resolution doesn't venture outside the class when a name is found in it.) – molbdnilo Mar 09 '16 at 15:01
  • @WingCuengRay I removed all of that code for a reason - it was completely irrelevant to, and distracting from, the question. You don't need any of the constructors or operators to demonstrate the problem you have. – Barry Mar 09 '16 at 15:16

1 Answers1

7
  1. Why can't I invoke swap without a using declaration?

We start in the nearest enclosing scope and work our way outwards until we find something. With this:

void H::swap(H &rhs)
{
    swap(*this, rhs);
}

Unqualified swap finds H::swap(). Then we do argument-dependent lookup. But the rule there is, from [basic.lookup.argdep]:

Let X be the lookup set produced by unqualified lookup (3.4.1) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains
— a declaration of a class member, or
— a block-scope function declaration that is not a using-declaration, or
— a declaration that is neither a function or a function template
then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the argument types as described below. [...]

Since the unqualified lookup set finds a class member, the argument-dependent lookup set is empty (that is, it doesnt find swap(H&, H&)).

  1. Why will the swap of my definition be invoked instead of the STL swap in the H::swap?

When you add:

void H::swap(H &rhs)
{
    using std::swap;
    swap(*this, rhs);
}

now unqualified swap finds std::swap() and not H::swap(), since the former is declared in a more inner scope. using std::swap; does not match any of the criteria in the above-stated rule that would lead to Y being empty (it's not a class member, it is a using-declaration, and it is a function template). As a result, the argument-dependent lookup set does include declarations found in associated namespaces - which includes swap(H&, H&) (since H is in the global namespace). We end up with two overload candidates - and yours is preferred since it's the non-template.


See Xeo's answer on the preferred way to add swap to your class. Basically, you want to write:

struct H {
    friend void swap(H&, H&) { ... }
};

This will be found by ADL (and only by ADL). And then whenever anybody calls swap correct:

using std::swap;
swap(a, b);

Lookup will find yours where appropriate.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • "Then we do argument-dependent lookup" ... "we don't do argument-dependent lookup" confusing answer! – curiousguy Mar 10 '16 at 00:28
  • Do you mean that The rule of [basic.lookup.argdep] dosen't apply because unqualified swap only finds the `std::swap` instead of `H::swap` in the inner scope, so both of the two candidates are found and the best matching is applied because of ADL? However, `void swap(H &, H &)` is defined in global scope instead of a namespace. It seems that ADL is not suitable without a namespace. – WingCuengRay Mar 10 '16 at 05:28
  • @WingCuengRay ADL looks in the associated namespaces of the argument types. In this case, that associated namespace is the global namespace. ADL is only about lookup - overload resolution is a separate process. – Barry Mar 10 '16 at 05:45
  • by the way, which book are the words -- "Let X ..."cited from? – WingCuengRay Mar 10 '16 at 14:22
  • @WingCuengRay The C++ standard, latest working draft. I added a link to a nice online linkable version. – Barry Mar 10 '16 at 14:26
  • Now I know that It is through the ADL that `void swap(H &, H &)` can be found. But I still have some misunderstanding of your words -- "Now, the earlier rule doesn't apply". Why? Does it break any of the three rules listed in [basic.lookup.argdep] above? If yes, which one? – WingCuengRay Mar 10 '16 at 15:07
  • @WingCuengRay It simply doesn't fit any of the criteria. See updated wording. – Barry Mar 10 '16 at 15:43