How does ADL affect this piece of C++ code?
Asked Answered
R

2

6

Actually, the below code can not be compiled with Clang using this command:

clang++ -std=c++11 test.cc -o test.

I just want to mimic the same behavior as "swapping idiom" in C++ to use "using-directive" to enable ADL. But where am I wrong with the following code? The expected calling priority should be the: N1::foo > N2::foo > ::foo, right?

namespace N1 {
  struct S {};
  void foo(S s) {
    std::cout << "called N1::foo.";
  }
}
namespace N2 {
  void foo(N1::S s) {
    std::cout << "called N2::foo.";
  }
}
void foo(N1::S s) {
  std::cout << "called foo.";
}
int main() {
  using N2::foo;  
  foo(N1::S{});
}

Error messages:

test.cc:54:3: error: call to 'foo' is ambiguous
  foo(N1::S{});
  ^~~
test.cc:40:8: note: candidate function
  void foo(S s) {
       ^
test.cc:45:8: note: candidate function
  void foo(N1::S s) {
       ^
1 error generated.

Updated:

I changed N2::foo to a template method which can mimic std::swap to some extend. So, the question here is why ::foo can not be called by "foo(N1::S{});" in the main function? Since the function should be much properer than a template function to be called when they have the same priority.

namespace N1 {
  struct S {};
  /*
  void foo(S s) {
    std::cout << "called N1::foo, specific one." << '\n';
  }
  */
}
namespace N2 {  // as a fallback to unqualified name which has no user-defined overload.
  template<typename T>
  void foo(T) {
    std::cout << "called N2::foo, generic one." << '\n';
  }
}
void foo(N1::S s) {
  std::cout << "called foo." << '\n';
}
int main() {
  using N2::foo;
  foo(N1::S{});
  foo(10);  // use generic version.
}
Ragan answered 20/8, 2020 at 6:12 Comment(0)
A
4

In this case, normal name lookup finds N2::foo, and N1::foo is found by ADL, they're both added to the overload set, then overload resolution is performed and the calling is ambiguous.

BTW: Without using N2::foo; in main(), ::foo will be found by normal name lookup, and N1::foo is found by ADL too; as the result the calling is still ambiguous.

Updated:

So, the question here is why ::foo can not be called by "foo(N1::S{});" in the main function?

Because with the usage of using N2::foo;, the name N2::foo is introduced in the main function. When calling foo the name N2::foo will be found at the scope of main, then name lookup stops, the further scope (the global namespace) won't be examined, so ::foo won't be found and added to overload set at all. As the result N2::foo is called for both cases.

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.

BTW: If you put using N2::foo; in global namespace before main, foo(N1::S{}); would call ::foo. Both N2::foo and ::foo are found by name lookup and ::foo wins in overload resolution.

LIVE

Army answered 20/8, 2020 at 6:19 Comment(2)
So, how does the using-directive (using std::swap) work in "swapping idiom" which does not have this problem?Ragan
@YHSPY Could you make a minimal example for it to show the problem about std::swap?Army
H
1

First you have the ordinary lookup that searches from inner scopes to outer scopes and stops at first match hiding overload from later scopes. Then, when ADL is triggered, it will add additional associated entities and namespaces to the search.

Thus, in your case, ADL lookup does not add anything to the overload set.

#include <iostream>

namespace N1 {
  struct S {};
  /*
  void foo(S s) {
    std::cout << "called N1::foo, specific one." << '\n';
  }
  */
}
namespace N2 { 
  template<typename T>
  void foo(T) {
    std::cout << "called N2::foo, generic one." << '\n';
  }
}
void foo(N1::S s) {
  std::cout << "called foo." << '\n';
}
int main() {
  using N2::foo;
  foo(N1::S{}); // ordinary lookup finds N2 in main scope and stops.
                // adl lookup add N1 ns to the additionnal ns set but finds nothing
                // overload set = N2::foo by ordinary lookup
}

Now, if I uncomment your S1 version, it wins! This is what you get:

#include <iostream>

namespace N1 {
  struct S {};
  void foo(S s) {
    std::cout << "called N1::foo, specific one." << '\n';
  }
}
namespace N2 { 
  template<typename T>
  void foo(T) {
    std::cout << "called N2::foo, generic one." << '\n';
  }
}
void foo(N1::S s) {
  std::cout << "called foo." << '\n';
}
int main() {
  using N2::foo;
  foo(N1::S{}); // ordinary lookup finds N2 in main scope and stops
                // adl lookup add N1 ns to the additionnal ns set and finds N1::foo
                // overload set = N2::foo by ordinary lookup, N1::foo by ADL, N1::foo wins
}

You get the same thing with swap. In this case, Foo::swap is used because it is in N1 namespace:

#include <iostream>

namespace N1 {
    struct Foo {

    };
    void swap(Foo& , Foo&) {
        std::cout << "swap Foo" << std::endl;
    }
}

namespace N2 {

struct S {
    N1::Foo f;
};

void swap(S& l,S& r) {
    using std::swap; // overload set is std::swap by ordinary lookup
    swap(l.f, r.f); // and Foo::swap by ADL, Foo::swap wins
}
}

int main() {
    N2::S s1,s2;
    swap(s1,s2);
}

But, if you move the Foo specific swap in the global ns, then std::swap get called:

#include <iostream>

namespace N1 {
    struct Foo {

    };
}

void swap(N1::Foo& , N1::Foo&) {
    std::cout << "swap Foo" << std::endl;
}

namespace N2 {

struct S {
    N1::Foo f;
};

void swap(S& l,S& r) {
    using std::swap; // overload set is std::swap by ordinary lookup
    swap(l.f, r.f); // because ADL does not add the global ns to the
                    // ns to be searched for
}
}

int main() {
    N2::S s1,s2;
    swap(s1,s2);
}
Hime answered 20/8, 2020 at 8:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.