Как ADL влияет на этот фрагмент кода C++?

На самом деле приведенный ниже код нельзя скомпилировать с помощью Clang с помощью этой команды:

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

Я просто хочу имитировать то же поведение, что и идиома подкачки в C++, чтобы использовать директиву использования для включения ADL. Но где я ошибаюсь в следующем коде? Ожидаемый приоритет вызова должен быть следующим: N1::fooN2::foo::foo, верно?

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{});
}

Сообщения об ошибках:

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.

Обновлено:

Я изменил N2::foo на метод шаблона, который может до некоторой степени имитировать std::swap. Итак, вопрос здесь в том, почему ::foo не может вызываться foo(N1::S{}); в функции main? Поскольку функция должна быть намного более правильной, чем функция шаблона, которую нужно вызывать, когда они имеют одинаковый приоритет.

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.
}

person YHSPY    schedule 20.08.2020    source источник


Ответы (2)


В этом случае обычный поиск имени находит N2::foo, а N1::foo находит ADL, они оба добавляются в набор перегрузки, затем выполняется разрешение перегрузки и призыв неоднозначный.

Кстати: без using N2::foo; в main() ::foo будет найдено обычным поиском имени, а N1::foo также будет найдено ADL; в результате вызов все еще неоднозначен.

Обновлено:

Итак, вопрос здесь в том, почему ::foo не может вызываться foo(N1::S{}); в функции main?

Поскольку при использовании using N2::foo; имя N2::foo вводится в функцию main. При вызове foo имя N2::foo будет найдено в области видимости main, затем поиск имени остановится, дальнейшая область видимости (глобальное пространство имен) не будет проверяться, поэтому ::foo вообще не будет найдено и добавлено в набор перегрузки. В результате в обоих случаях вызывается N2::foo.

поиск имени проверяет области действия, как описано ниже, пока не найдет хотя бы одно объявление любого вида, после чего поиск останавливается и дальнейшие области не проверяются.

Кстати: если вы поместите using N2::foo; в глобальное пространство имен перед main, foo(N1::S{}); вызовет ::foo. И N2::foo, и ::foo находятся при поиске по имени, а ::foo выигрывает в разрешении перегрузки.

ПРЯМОЙ ЭФИР

person songyuanyao    schedule 20.08.2020
comment
Итак, как работает директива using (используя std::swap) в идиоме подкачки, в которой нет этой проблемы? - person YHSPY; 20.08.2020
comment
@YHSPY Не могли бы вы сделать минимальный пример, чтобы показать проблему с std::swap? - person songyuanyao; 20.08.2020

Во-первых, у вас есть обычный поиск, который ищет из внутренних областей во внешние области и останавливается при первом совпадении, скрывая перегрузку из более поздних областей. Затем, когда активируется ADL, он добавит в поиск дополнительные связанные объекты и пространства имен.

Таким образом, в вашем случае поиск ADL ничего не добавляет к набору перегрузки.

#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
}

Теперь, если я раскомментирую вашу версию S1, она победит! Это то, что ты получаешь:

#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
}

Вы получаете то же самое с свопом. В данном случае используется Foo::swap, поскольку он находится в пространстве имен N1:

#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);
}

Но если вы переместите специфичный для Foo своп в глобальный ns, то будет вызван std::swap:

#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);
}
person nop666    schedule 20.08.2020