Home > Net >  Why does 'using std::swap' fix an ambiguous swap call?
Why does 'using std::swap' fix an ambiguous swap call?

Time:12-15

code:

#include <iostream>
    
namespace N1 {
    struct A {
        int x;
    };
    void swap(N1::A& a1, N1::A& a2) noexcept {
        std::cout << "N1\n";
    }
}
    
namespace std {
    template<>
    void swap(N1::A&, N1::A&) noexcept {
        std::cout << "std\n";
    }
}
    
namespace N2 {
    void swap(N1::A& a1, N1::A& a2) noexcept {
        std::cout << "N2\n";
    }
    
    void foo(N1::A& a1, N1::A& a2) {
        //using std::swap; // or any another template swap
        swap(a1,a2);
    }
}
    
int main(int argc, char* argv[]) 
{   
    N1::A a, b;
    N2::foo(a,b);
}

The compiler error:

try4.cpp:28:5: error: call to 'swap' is ambiguous
    swap(a1,a2);
    ^~~~
try4.cpp:9:6: note: candidate function
void swap(N1::A& a1, N1::A& a2) noexcept {
     ^
try4.cpp:22:6: note: candidate function
void swap(N1::A& a1, N1::A& a2) noexcept {
     ^
1 error generated.

Which is kind of clear. But if I add any swap template (like uncommenting line:27 - using std::swap) in the scope, it works fine and N1::swap will be called at Line:28, which is intended.

working version:

...
void foo(N1::A& a1, N1::A& a2) {
    using std::swap; // or any another template swap
    swap(a1,a2);
}
...

Thank you for providing any hints why it works like that.

CodePudding user response:

This is due to argument-dependent lookup.

Essentially, because type A is used as a parameter the compiler looks inside N1 for matching functions because that is where type A was defined.

The rules for how this works is fairly complex. If you want to simply avoid the error, you could qualify your swap call, to N2::swap for example.

There are more details about this feature here: https://en.cppreference.com/w/cpp/language/adl

The link also uses std::swap as an example:

ADL is the reason behind the established idiom for swapping two objects in generic code: using std::swap; swap(obj1, obj2); because calling std::swap(obj1, obj2) directly would not consider the user-defined swap() functions that could be defined in the same namespace as the types of obj1 or obj2, and just calling the unqualified swap(obj1, obj2) would call nothing if no user-defined overload was provided. In particular, std::iter_swap and all other standard library algorithms use this approach when dealing with Swappable types.

CodePudding user response:

I think the question is not stated very clear and it is pointing towards a possible issue.

The way this works is that without using std::swap the code doesn't compile because N1::swap and N2::swap are ambiguous in this scope, but with using std::swap the ambiguity is resolved and N1::swap is called for whatever reason. Also when one writes using xx::swap where xx::swap is not a template there is still an ambiguity... albeit a different one, ambiguity between xx::swap and N1::swap.

What happens is that writing using xx::swap lets xx::swap prioritize over local N2::swap but it will still trigger ambiguity with the N1::swap which is found via ADL rules. In the template swap case, the template swap has lower priority than N1::swap that is located in the same namespace as N1::A and thus no ambiguity is created.

This is confusing and misleading and might be an issue in the language. Just recently, in C 20 was added an ADL rule to fix some unnecessary ambiguities. Perhaps, one should take a look in this situation as well as the behavior is misleading but it might be a necessary part of language as the current situation is an edge case and it shouldn't be happening normally in the code.

  •  Tags:  
  • c
  • Related