Co to jest „wyszukiwanie zależne od argumentów” (inaczej ADL lub „wyszukiwanie Koeniga”)?


176

Jakie są dobre wyjaśnienia, czym jest wyszukiwanie zależne od argumentów? Wiele osób nazywa to również Koenig Lookup.

Najlepiej chciałbym wiedzieć:

  • Dlaczego to dobrze?
  • Dlaczego to jest zła rzecz?
  • Jak to działa?




Odpowiedzi:


223

Koenig Lookup lub Argument Dependent Lookup opisuje, jak niekwalifikowane nazwy są wyszukiwane przez kompilator w C ++.

Standard C ++ 11, § 3.4.2 / 1, stwierdza:

Gdy wyrażenie postfiksowe w wywołaniu funkcji (5.2.2) jest niekwalifikowanym identyfikatorem, można przeszukiwać inne przestrzenie nazw nieuwzględniane podczas zwykłego wyszukiwania bez kwalifikacji (3.4.1), aw tych przestrzeniach nazw deklaracje funkcji zaprzyjaźnionych z zakresem przestrzeni nazw ( 11.3), które nie są widoczne w inny sposób. Te modyfikacje wyszukiwania zależą od typów argumentów (aw przypadku argumentów szablonu szablonu, przestrzeni nazw argumentu szablonu).

Mówiąc prościej, Nicolai Josuttis stwierdza 1 :

Nie musisz kwalifikować przestrzeni nazw dla funkcji, jeśli jeden lub więcej typów argumentów jest zdefiniowanych w przestrzeni nazw funkcji.

Prosty przykład kodu:

namespace MyNamespace
{
    class MyClass {};
    void doSomething(MyClass);
}

MyNamespace::MyClass obj; // global object


int main()
{
    doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}

W powyższym przykładzie nie ma ani using-declaration ani -directive, usingale mimo to kompilator poprawnie identyfikuje niekwalifikowaną nazwę doSomething()jako funkcję zadeklarowaną w przestrzeni nazw MyNamespace, stosując wyszukiwanie Koeniga .

Jak to działa?

Algorytm mówi kompilatorowi, aby nie tylko sprawdzał zakres lokalny, ale także przestrzenie nazw zawierające typ argumentu. Zatem w powyższym kodzie kompilator stwierdza, że ​​obiekt objbędący argumentem funkcji doSomething()należy do przestrzeni nazw MyNamespace. Dlatego sprawdza tę przestrzeń nazw, aby zlokalizować deklarację doSomething().

Jaka jest zaleta wyszukiwania Koenig?

Jak pokazuje powyższy prosty przykład kodu, wyszukiwanie Koeniga zapewnia programiście wygodę i łatwość użycia. Bez wyszukiwania Koenig programista usingmusiałby obciążać narzut, aby wielokrotnie określać w pełni kwalifikowane nazwy, lub zamiast tego używać wielu -deklaracji.

Skąd krytyka wyszukiwania Koeniga?

Nadmierne poleganie na wyszukiwaniu Koenig może prowadzić do problemów semantycznych i czasami zaskakiwać programistę.

Rozważmy przykład std::swap, który jest standardowym algorytmem biblioteki do zamiany dwóch wartości. W przypadku wyszukiwania Koeniga należałoby zachować ostrożność podczas korzystania z tego algorytmu, ponieważ:

std::swap(obj1,obj2);

mogą nie zachowywać się tak samo jak:

using std::swap;
swap(obj1, obj2);

W przypadku ADL to, która wersja swapfunkcji zostanie wywołana, zależy od przestrzeni nazw przekazywanych do niej argumentów.

Jeśli istnieje przestrzeń nazw A, a jeśli A::obj1, A::obj2i A::swap()istnieje wówczas drugi przykład spowoduje wywołanie A::swap(), który może nie być czego chce użytkownik.

Ponadto, jeśli z jakiegoś powodu oba A::swap(A::MyClass&, A::MyClass&)i std::swap(A::MyClass&, A::MyClass&)są zdefiniowane, to pierwszy przykład zostanie wywołany, std::swap(A::MyClass&, A::MyClass&)ale drugi nie zostanie skompilowany, ponieważ swap(obj1, obj2)byłby niejednoznaczny.

Drobnostki:

Dlaczego nazywa się to „wyszukiwaniem Koeniga”?

Ponieważ został opracowany przez byłego badacza i programistę AT&T i Bell Labs, Andrew Koeniga .

Czytaj dalej:


1 Definicja wyszukiwania Koeniga jest taka, jak zdefiniowano w książce Josuttisa, The C ++ Standard Library: A Tutorial and Reference .


11
@AlokSave: +1 za odpowiedź, ale ciekawostki nie są poprawne. Koenig nie wynalazł ADL, jak wyznaje tutaj :)
legends2k 28.07.14

20
Przykład krytyki algorytmu Koeniga można traktować jako „cechę” wyszukiwania Koeniga w takim samym stopniu, jak „con”. Używanie std :: swap () w taki sposób jest powszechnym idiomem: Podaj 'using std :: swap ()' na wypadek, gdyby nie została dostarczona bardziej wyspecjalizowana wersja A :: swap (). Jeśli dostępna jest wyspecjalizowana wersja A :: swap (), normalnie chcielibyśmy , aby była ona wywoływana. Zapewnia to większą ogólność dla wywołania swap (), ponieważ możemy zaufać wywołaniu w zakresie kompilacji i działania, ale możemy również ufać bardziej wyspecjalizowanej wersji, która będzie używana, jeśli taka istnieje.
Anthony Hall

6
@anthrond Jest w tym coś więcej. W std::swaprzeczywistości musisz to zrobić, ponieważ jedyną alternatywą byłoby dodanie std::swapjawnej specjalizacji funkcji szablonu dla Twojej Aklasy. Jednak jeśli twoja Aklasa jest sama w sobie szablonem, byłaby to częściowa specjalizacja, a nie jawna specjalizacja. Częściowa specjalizacja funkcji szablonu jest niedozwolona. Dodanie przeciążenia std::swapbyłoby alternatywą, ale jest wyraźnie zabronione (nie możesz dodawać rzeczy do stdprzestrzeni nazw). Tak więc ADL to jedyny sposób std::swap.
Adam Badura,

1
Spodziewałbym się wzmianki o przeciążonych operatorach w sekcji „Zaleta wyszukiwania koenig”. przykład z std::swap()wydaje się nieco cofnięty. Spodziewam się problem daj się, gdy std::swap()wybrano zamiast specyficznego przeciążeniowym do typu A::swap(). Przykład z std::swap(A::MyClass&, A::MyClass&)wydaje się mylący. ponieważ stdnigdy nie miałoby określonego przeciążenia dla typu użytkownika, nie sądzę, że jest to świetny przykład.
Arvid

1
@gsamaras ... I? Wszyscy widzimy, że funkcja nigdy nie została zdefiniowana. Twój komunikat o błędzie dowodzi, że zadziałał, ponieważ szuka MyNamespace::doSomething, a nie tylko ::doSomething.
Załóż pozew Moniki,

69

W Koenig Lookup, jeśli wywoływana jest funkcja bez określenia jej przestrzeni nazw, wówczas nazwa funkcji jest również przeszukiwana w przestrzeni (ach) nazw, w których zdefiniowano typ argumentu (ów). Dlatego jest również znany jako Wyszukiwanie nazw zależne od argumentów , w skrócie po prostu ADL .

To dzięki Koenig Lookup możemy napisać tak:

std::cout << "Hello World!" << "\n";

W przeciwnym razie musielibyśmy napisać:

std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");

co naprawdę jest zbyt długie, a kod wygląda naprawdę brzydko!

Innymi słowy, przy braku Koenig Lookup nawet program Hello World wygląda na skomplikowany.


12
Przekonujący przykład.
Anthony Hall

10
@AdamBadura: Zwróć uwagę, że std::coutjest to jeden argument funkcji, który wystarczy, aby włączyć ADL. Zauważyłeś to?
Nawaz

1
@meet: Twoje pytanie wymaga długiej odpowiedzi, której nie można udzielić w tym miejscu. Mogę więc tylko doradzić czytanie na tematy takie jak: 1) podpis ostream<<(jak w tym, co przyjmuje jako argumenty i co zwraca). 2) W pełni kwalifikowane nazwy (takie jak std::vectorlub std::operator<<). 3) Bardziej szczegółowe badanie wyszukiwania zależnego od argumentów.
Nawaz,

2
@WorldSEnder: Tak, masz rację. Funkcja, która może przyjąć std::endljako argument, jest w rzeczywistości funkcją składową. W każdym razie, jeśli użyję "\n"zamiast std::endl, moja odpowiedź jest prawidłowa. Dziękuję za komentarz.
Nawaz

2
@Destructor: Ponieważ wywołanie funkcji w postaci f(a,b)wywołuje funkcję wolną . Więc w przypadku std::operator<<(std::cout, std::endl);nie ma takiej wolnej funkcji, która przyjmuje std::endljako drugi argument. Jest to funkcja składowa, która przyjmuje std::endljako argument i dla której musisz napisać std::cout.operator<<(std::endl);. a ponieważ istnieje wolna funkcja, która przyjmuje char const*jako drugi argument, "\n"działa; '\n'zadziała również.
Nawaz

30

Może najlepiej zacząć od tego, dlaczego, a dopiero potem przejść do tego, jak.

Kiedy wprowadzono przestrzenie nazw, chodziło o to, aby wszystko było zdefiniowane w przestrzeniach nazw, aby oddzielne biblioteki nie kolidowały ze sobą. Jednak spowodowało to problem z operatorami. Spójrz na przykład na następujący kod:

namespace N
{
  class X {};
  void f(X);
  X& operator++(X&);
}

int main()
{
  // define an object of type X
  N::X x;

  // apply f to it
  N::f(x);

  // apply operator++ to it
  ???
}

Oczywiście mogłeś napisać N::operator++(x), ale to pokonałoby cały sens przeciążenia operatora. Dlatego trzeba było znaleźć rozwiązanie, które pozwoli kompilatorowi znaleźć, operator++(X&)mimo że nie było to w zakresie. Z drugiej strony, nadal nie powinien znaleźć innej operator++zdefiniowanej w innej, niepowiązanej przestrzeni nazw, która może sprawić, że wywołanie będzie niejednoznaczne (w tym prostym przykładzie nie uzyskasz niejednoznaczności, ale w bardziej złożonych przykładach możesz). Rozwiązaniem było wyszukiwanie zależne od argumentu (ADL), nazwane w ten sposób, ponieważ wyszukiwanie zależy od argumentu (a dokładniej od typu argumentu). Ponieważ schemat został wymyślony przez Andrew R. Koeniga, jest również często nazywany wyszukiwaniem Koeniga.

Sztuczka polega na tym, że w przypadku wywołań funkcji, oprócz zwykłego wyszukiwania nazw (które znajduje nazwy w zakresie w miejscu użycia), wykonywane jest drugie wyszukiwanie w zakresach typów wszelkich argumentów nadanych funkcji. Tak więc w powyższym przykładzie, jeśli piszesz x++w głównym, szuka operator++nie tylko w zakresie globalnym, ale także w zakresie, w którym typ x, N::Xzostał zdefiniowany, tj namespace N. I tam znajduje dopasowanie operator++i dlatego x++po prostu działa. Jednak operator++, powiedzmy N2, inna zdefiniowana w innej przestrzeni nazw nie zostanie znaleziona. Ponieważ ADL nie jest ograniczony do przestrzeni nazw, możesz również użyć f(x)zamiast N::f(x)in main().


Dzięki! Nigdy tak naprawdę nie rozumiałem, dlaczego tam jest!
user965369

20

Moim zdaniem nie wszystko w tym jest dobre. Ludzie, w tym sprzedawcy kompilatorów, obrażają go z powodu jego czasami niefortunnego zachowania.

ADL jest odpowiedzialny za gruntowny przegląd pętli for-range w C ++ 11. Aby zrozumieć, dlaczego ADL może czasami mieć niezamierzone skutki, weź pod uwagę nie tylko przestrzenie nazw, w których definiowane są argumenty, ale także argumenty argumentów szablonów argumentów, typów parametrów typów funkcji / typów wskaźników typów wskaźników tych argumentów i tak dalej i dalej.

Przykład użycia boost

std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);

Skutkowało to niejednoznacznością, jeśli użytkownik korzysta z biblioteki boost.range, ponieważ zarówno std::beginzostała znaleziona (przez ADL przy użyciu std::vector), jak i boost::beginznaleziona (przez ADL przy użyciu boost::shared_ptr).


Zawsze zastanawiałem się, jakie korzyści przynosi rozważenie argumentów opartych na szablonach.
Dennis Zickefoose

Czy można powiedzieć, że ADL jest zalecany tylko dla operatorów i lepiej jest pisać przestrzenie nazw jawnie dla innych funkcji?
balki

Czy uwzględnia również przestrzenie nazw klas bazowych argumentów? (to byłoby szalone, gdyby tak było, oczywiście).
Alex B

3
jak naprawić? użyj std :: begin?
paulm

2
@paulm Tak, std::beginczyści niejednoznaczność przestrzeni nazw.
Nikos,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.