Argument Dependent Lookup
Z racji tego, że czas na cokolwiek ostatnio mam jedynie wtedy gdy mój fork jest w trakcie wywoływania funkcji sleep, a wszystkie jego syscalle zdają się przechodzić przeze mnie, a nie przez kernel (dziwne, bo nie przypominam sobie, żebym w celach debugowych wywoływał na nim ptrace), muszę dość mocno ograniczyć zakres i objętość artykułów. Dlatego tym razem postaram się w paru żółnierskich słowach wytłumaczyć czym w C++ jest Argument Dependent Lookup. Nie chcę przy tym powiedzieć, że temat ADL jest prosty (nie jest) i łatwy do zrozumienia i zapamiętania (również nie jest), ale samą ideę można według mnie wytłumaczyć w dość prosty sposób.
Do rzeczy
ADL jest dodatkowym sposobem poszukiwania przez kompilator wywoływanych nazw funkcji w przypadku gdy zwyczajowe przeszukiwanie zawiedzie (pokrótce je opisałem przy okazji tematu ukrywania nazw). ADL, jak nazwa sugeruje, analizuje argumenty funkcji, a konkretnie ich namespace’y, i przeszukuje je pod kątem zgodności wywołania i deklaracji funkcji.
Innymi słowy, świadomie możemy pominąć namespace’y danej funkcji przy jej wywołaniu, pod warunkiem, że jej argumenty leżą w tej samej przestrzeni nazw co ona sama.
Przykładowo:
namespace foo
{
class A {};
int add(int a, int b, A&) { return a + b; }
namespace bar
{
class B {};
int prod(int a, int b, A&) { return a * b; }
int prod(int a, int b, B&) { return a * b; }
} // namespace bar
} // namespace foo
int main()
{
foo::A a;
foo::bar::B b;
auto i = add(1, 2, a);
//auto j = add(3, 4, b); // error
auto k = prod(7, 8, b);
// auto l = prod(5, 6, a); // error
}
Jak widać, jedynie bezpośrednie namespace’y argumentów są dodawane do przeszukiwania. Oczywiście, nie poczuwam się do odgrywania roli podręcznika C++, a reguł jest znacznie więcej (np. w przypadku gdy argumentem jest wskaźnik na funkcję, to przeszukiwane są również wartości zwracane wraz ze zbiorem powiązanych z nimi klas i namespace’ów). Po szczegóły odsyłam do faktycznego podręcznika: cppreference.com.
swap
Idiom copy-and-swap na stałe zagościł w narzędziowniku programistów C++. W
telegraficznym skrócie: dla klas, dla których piszemy konstruktor kopiujący
powinniśmy również utworzyć publiczną wolną funkcję swap
. Wynika to np. z
tego, że cała biblioteka standardowa wykorzystuje ADL dla wywołań funkcji
swap
. Przebiega to następująco:
template <typename Iter1, typename Iter2>
void iter_swap(Iter1 lhs, Iter2 rhs)
{
using std::swap;
swap(*lhs, *rhs);
}
Spowoduje to wywołanie specjalizowanej wersji swapa (przeszukane zostaną
namespace’y odpowiednie dla *lhs
i *rhs
), a w przypadku gdyby owa się nie
znalazła, dzięki użyciu using std::swap
istnieje możliwość cichego odwrotu w
stronę domyślnej implementacji z biblioteki standardowej.
Interfejsy
Funkcje odnajdywane przez ADL są uznawane za część interfejsu klas, które są ich
parametrami. Dlaczego ma to sens? Wyobraźmy sobie, że chcielibyśmy np. wypisać
tekstową reprezentację jakiejś klasy. Jak to robimy? Na przykład przeładowujemy
operator<<
:
namespace foo
{
struct Foo
{
std::string val;
}
std::ostream& operator<<(std::ostream& s, const Foo& foo)
{
os << "Foo(val=" << foo.val << ")";
return os;
}
} // namespace foo
int main()
{
Foo foo{"abc"};
std::cout << "my foo: " << foo << std::endl;
}
W powyższym operator<<
zawiera tak naprawdę newralgiczny fragment interfejsu
Foo
, czyli prezentację owej struktury użytkownikowi. Funkcja ta jest natomiast
odnajdywana tylko i wyłącznie dzięki ADL, gdyż cout <<
jest tak naprawdę
niekwalifikowanym wywołaniem operator<<
.
Innymi słowy:
std::cout << "my foo: " << foo << std::endl;
jest równoważne
operator<<(std::cout, "my foo: ");
operator<<(std::cout, foo);
endl(std::cout);
Warto o tym pamiętać, gdyż każda zmiana funkcji odnajdywanych przez ADL jest de
facto zmianą interfejsu klasy i może być niekompatybilna wstecznie, albo
spowodować błędne lub niespodziewane działanie programu. Na przykład usunięcie
specjalizowanej implementacji swap
nie zepsuje kompilacji programu, a raczej
spowoduje wycofanie się do std::swap
, który będzie zamieniał przekazane
wartości w inny sposób niż odbywało się to wcześniej.