Wyszukiwanie nazw zależne od argumentów

W języku programowania C++ wyszukiwanie zależne od argumentów ( ADL ) lub zależne od argumentów wyszukiwanie nazw dotyczy wyszukiwania niekwalifikowanej nazwy funkcji w zależności od typów argumentów podanych wywołaniu funkcji . To zachowanie jest również znane jako wyszukiwanie Koeniga , ponieważ często przypisuje się je Andrew Koenigowi , chociaż nie jest on jego wynalazcą.

mogą być przeszukiwane inne przestrzenie nazw, które nie są brane pod uwagę podczas normalnego wyszukiwania, gdzie zbiór przestrzeni nazw do przeszukania zależy od typów argumentów funkcji. W szczególności zestaw deklaracji wykrytych podczas procesu ADL i brany pod uwagę przy rozwiązywaniu nazwy funkcji jest połączeniem deklaracji znalezionych przez normalne wyszukiwanie z deklaracjami znalezionymi przez przeszukanie zestawu przestrzeni nazw powiązanych z typami argumentów funkcji .

Przykład

Przykład ADL wygląda następująco:

  

  

     

  

  
    
    0  
 przestrzeń nazw  NS  {  klasa  A  {};  void  f  (  A  &  a  ,  int  i  )  {}  }  // przestrzeń nazw NS  int  main  ()  {  NS  ::  A  a  ;  fa  (  za  ,  );  // Wywołuje NS::f.  } 

Mimo że główna funkcja nie znajduje się w przestrzeni nazw NS ani w przestrzeni nazw NS w zakresie, funkcja NS::f(A&, int) została znaleziona ze względu na zadeklarowane typy rzeczywistych argumentów w instrukcji wywołania funkcji.

Typowym wzorcem w standardowej bibliotece C++ jest deklarowanie przeciążonych operatorów, które zostaną znalezione w ten sposób. Na przykład ten prosty Hello World nie skompilowałby się, gdyby nie ADL:

 
 

  
     
    
 #include  <iostream>  #include  <string>  int  main  ()  {  std  ::  string  str  =  "witaj świecie"  ;  std  ::  cout  <<  str  ;  } 

Użycie << jest równoznaczne z wywołaniem operatora << bez kwalifikatora std:: . Jednak w tym przypadku przeciążenie operatora<<, które działa dla łańcucha , znajduje się w przestrzeni nazw std , więc do jego użycia wymagany jest ADL.

Poniższy kod działałby bez ADL (który i tak jest do niego stosowany):

 

  
    
 #include  <iostream>  int  main  ()  {  std  ::  cout  <<  5  ;  } 

Działa, ponieważ operator wyjściowy dla liczb całkowitych jest funkcją składową klasy std::ostream , która jest typem cout . W związku z tym kompilator interpretuje to stwierdzenie jako

 std  ::  cout  .  operator  <<  (  5  ); 

które może rozwiązać podczas normalnego wyszukiwania. Należy jednak wziąć pod uwagę, że np. const char * przeciążony operator<< jest funkcją nieczłonkowską w przestrzeni nazw std i dlatego wymaga narzędzia ADL do poprawnego wyszukiwania:


 


 
 /* wypisze dostarczony ciąg znaków zgodnie z oczekiwaniami, używając narzędzia ADL wyprowadzonego z typu argumentu std::cout */  operator  <<  (  std  ::  cout  ,  "Cześć"  )  /* wywołuje funkcję składową ostream operatora << biorąc void const*,  która wypisze adres podanego ciągu znaków zamiast zawartości ciągu znaków */  std  ::  cout  .  operator  <<  (  "Cześć"  ) 

przeciążona przestrzeń nazw std operator << operator << przeciążona w przestrzeni nazw:


   /*odpowiednik operatora<<(std::cout, str). Kompilator przeszukuje przestrzeń nazw std za pomocą ADL ze względu na typ std::string parametru str i std::cout */   std  ::  cout  <<  str  ; 

Jak Koenig wskazuje w osobistej notatce, bez ADL kompilator wskazałby błąd stwierdzający, że nie może znaleźć operatora << , ponieważ instrukcja nie określa wyraźnie, że został znaleziony w przestrzeni nazw std .

Interfejsy

Funkcje znalezione przez ADL są uważane za część interfejsu klasy. W Bibliotece standardowej C++ kilka algorytmów używa niekwalifikowanych wywołań do zamiany z przestrzeni nazw std . W rezultacie ogólna std::swap jest używana, jeśli nic innego nie zostanie znalezione, ale jeśli te algorytmy są używane z klasą innej firmy, Foo , znalezioną w innej przestrzeni nazw, która również zawiera swap(Foo&, Foo&) , to przeciążenie zostanie zastosowany swap .

Krytyka

Podczas gdy ADL sprawia, że ​​funkcje zdefiniowane poza klasą mogą zachowywać się tak, jakby były częścią interfejsu tej klasy, sprawia, że ​​przestrzenie nazw są mniej restrykcyjne i mogą wymagać użycia w pełni kwalifikowanych nazw, gdy w przeciwnym razie nie byłyby potrzebne. Na przykład standardowa biblioteka C++ szeroko wykorzystuje niekwalifikowane wywołania std::swap w celu zamiany dwóch wartości. Chodzi o to, że można wtedy zdefiniować własną wersję wymiany we własnej przestrzeni nazw i będzie ona używana w algorytmach standardowej biblioteki. Innymi słowy, zachowanie

  

  

  

 
 

  przestrzeń nazw  N  {  struktura  A  {};  }  // przestrzeń nazw N  A  a  ;  A  b  ;  std  ::  zamiana  (  a  ,  b  ); 

może, ale nie musi być takie samo jak zachowanie

 
  używając  std  ::  swap  ;  zamień  (  a  ,  b  ); 

(gdzie a i b są typu N::A ), ponieważ jeśli N::swap(N::A&, N::A&) istnieje, drugi z powyższych przykładów wywoła go, podczas gdy pierwszy nie. Ponadto, jeśli z jakiegoś powodu zarówno N::swap(N::A&, N::A&) jak i std::swap(N::A&, N::A&) są zdefiniowane, to pierwszy przykład wywoła std:: swap(N::A&, N::A&), ale drugi nie zostanie skompilowany, ponieważ swap(a, b) byłby niejednoznaczny.

Ogólnie rzecz biorąc, nadmierna zależność od ADL może prowadzić do problemów semantycznych . Jeśli jedna biblioteka, L1 , oczekuje, że niekwalifikowane wywołania foo(T) będą miały jedno znaczenie, a inna biblioteka, L2 oczekuje, że będzie miała inne, wtedy przestrzenie nazw tracą swoją użyteczność. Jeśli jednak L1 oczekuje , że L1::foo(T) będzie miało jedno znaczenie, a L2 robi to samo, to nie ma konfliktu, ale wywołania foo(T) musiałyby być w pełni kwalifikowane (tj. L1::foo(x) w przeciwieństwie do używania L1::foo; foo(x); ), aby ADL nie przeszkadzał.

Linki zewnętrzne