Che cos'è la "Ricerca dipendente dall'argomento" (aka ADL o "Ricerca Koenig")?


176

Quali sono alcune buone spiegazioni su quale sia la ricerca dipendente dall'argomento? Molte persone lo chiamano anche Koenig Lookup.

Preferibilmente vorrei sapere:

  • Perché è una buona cosa?
  • Perché è una brutta cosa?
  • Come funziona?




Risposte:


223

Koenig Lookup , o Argument Dependent Lookup , descrive come i nomi non qualificati vengono cercati dal compilatore in C ++.

Lo standard C ++ 11 § 3.4.2 / 1 afferma:

Quando l'espressione postfisso in una chiamata di funzione (5.2.2) è un ID non qualificato, è possibile ricercare altri spazi dei nomi non considerati durante la normale ricerca non qualificata (3.4.1) e, in tali spazi dei nomi, dichiarazioni di funzione amico dello spazio dei nomi ( 11.3) non altrimenti visibile può essere trovato. Queste modifiche alla ricerca dipendono dai tipi di argomenti (e per gli argomenti del modello di modello, lo spazio dei nomi dell'argomento del modello).

In termini più semplici Nicolai Josuttis afferma 1 :

Non è necessario qualificare lo spazio dei nomi per le funzioni se uno o più tipi di argomenti sono definiti nello spazio dei nomi della funzione.

Un semplice esempio di codice:

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

MyNamespace::MyClass obj; // global object


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

Nell'esempio precedente non esiste né una usingdichiarazione né una usingdirettiva, ma il compilatore identifica correttamente il nome non qualificato doSomething()come funzione dichiarata nello spazio MyNamespacedei nomi applicando la ricerca Koenig .

Come funziona?

L'algoritmo dice al compilatore di guardare non solo all'ambito locale, ma anche agli spazi dei nomi che contengono il tipo di argomento. Pertanto, nel codice precedente, il compilatore rileva che l'oggetto obj, che è l'argomento della funzione doSomething(), appartiene allo spazio dei nomi MyNamespace. Quindi, cerca quello spazio dei nomi per individuare la dichiarazione di doSomething().

Qual è il vantaggio della ricerca Koenig?

Come dimostra il semplice esempio di codice sopra riportato, la ricerca Koenig offre praticità e facilità d'uso al programmatore. Senza la ricerca di Koenig ci sarebbe un sovraccarico sul programmatore, per specificare ripetutamente i nomi pienamente qualificati o, invece, usare numerose usingdichiarazioni.

Perché le critiche alla ricerca di Koenig?

L'eccessiva dipendenza dalla ricerca di Koenig può portare a problemi semantici e talvolta sorprendere il programmatore.

Considera l'esempio di std::swap, che è un algoritmo di libreria standard per scambiare due valori. Con la ricerca Koenig si dovrebbe essere cauti durante l'utilizzo di questo algoritmo perché:

std::swap(obj1,obj2);

potrebbe non mostrare lo stesso comportamento di:

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

Con ADL, quale versione della swapfunzione viene chiamata dipende dallo spazio dei nomi degli argomenti passati ad essa.

Se esiste uno spazio dei nomi Ae se A::obj1, A::obj2ed A::swap()esiste, il secondo esempio si tradurrà in una chiamata A::swap(), che potrebbe non essere quella desiderata dall'utente.

Inoltre, se per qualche motivo entrambi A::swap(A::MyClass&, A::MyClass&)e std::swap(A::MyClass&, A::MyClass&)sono definiti, il primo esempio chiamerà std::swap(A::MyClass&, A::MyClass&)ma il secondo non verrà compilato perché swap(obj1, obj2)sarebbe ambiguo.

curiosità:

Perché si chiama "ricerca Koenig"?

Perché è stato ideato dall'ex ricercatore e programmatore AT&T e Bell Labs, Andrew Koenig .

Ulteriori letture:


1 La definizione della ricerca Koenig è come definita nel libro di Josuttis, The C ++ Standard Library: A Tutorial and Reference .


11
@AlokSave: +1 per la risposta, ma la curiosità non è corretta. Koenig non ha inventato l'ADL, come confessa qui :)
legends2k,

20
L'esempio nella critica dell'algoritmo di Koenig può essere considerato una "caratteristica" della ricerca di Koenig tanto quanto una "truffa". L'uso di std :: swap () in questo modo è un linguaggio comune: fornire un 'utilizzo di std :: swap ()' nel caso in cui non venga fornita una versione più specializzata A :: swap (). Se è disponibile una versione specializzata di A :: swap (), normalmente vorremmo che fosse chiamata. Ciò fornisce più genericità per la chiamata swap (), poiché possiamo fidarci della chiamata per compilare e lavorare, ma possiamo anche fidarci della versione più specializzata da usare se ce n'è una.
Anthony Hall,

6
@anthrond C'è di più in questo. Con std::swapquello in realtà devi farlo poiché l'unica alternativa sarebbe quella di aggiungere la std::swapfunzione esplicita alla specializzazione del modello per la tua Aclasse. Tuttavia, se la tua Aclasse è un modello in sé, sarebbe una specializzazione parziale piuttosto che una specializzazione esplicita. E la specializzazione parziale della funzione template non è consentita. L'aggiunta di overload di std::swapsarebbe un'alternativa ma è esplicitamente vietata (non è possibile aggiungere elementi allo stdspazio dei nomi). Quindi ADL è l'unico modo per std::swap.
Adam Badura,

1
Mi sarei aspettato di vedere una menzione di operatori sovraccarichi sotto "vantaggio della ricerca koenig". l'esempio con std::swap()sembra un po 'indietro. Mi aspetto che il problema si verifichi quando std::swap()viene selezionato anziché il sovraccarico specifico del tipo A::swap(). L'esempio con std::swap(A::MyClass&, A::MyClass&)sembra fuorviante. dal momento stdche non avrebbe mai un sovraccarico specifico per un tipo di utente, non credo sia un ottimo esempio.
Arvid,

1
@gsamaras ... E? Tutti possiamo vedere che la funzione non è mai stata definita. Il tuo messaggio di errore dimostra che ha funzionato, in realtà, perché sta cercando MyNamespace::doSomething, non solo ::doSomething.
Finanzi la causa di Monica il

69

In Koenig Lookup, se una funzione viene chiamata senza specificare il suo spazio dei nomi, viene cercato anche il nome di una funzione negli spazi dei nomi in cui è definito il tipo degli argomenti. Questo è il motivo per cui è anche noto come Ricerca dipendente dall'argomento , in breve semplicemente ADL .

È grazie a Koenig Lookup, possiamo scrivere questo:

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

Altrimenti, dovremmo scrivere:

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

che è davvero troppo digitando e il codice sembra davvero brutto!

In altre parole, in assenza di Koenig Lookup, anche un programma Hello World sembra complicato.


12
Esempio persuasivo.
Anthony Hall,

10
@AdamBadura: si noti che std::coutè un argomento della funzione, che è sufficiente per abilitare ADL. L'hai notato?
Nawaz,

1
@meet: la tua domanda ha bisogno di una risposta lunga che non può essere fornita in questo spazio. Quindi posso solo consigliarti di leggere su argomenti come: 1) firma di ostream<<(come in ciò che prende come argomenti e ciò che restituisce). 2) Nomi pienamente qualificati (come std::vectoro std::operator<<). 3) Uno studio più dettagliato della ricerca dipendente dall'argomento.
Nawaz,

2
@WorldSEnder: Sì, hai ragione. La funzione che può assumere std::endlcome argomento è in realtà una funzione membro. Comunque, se uso "\n"invece di std::endl, allora la mia risposta è corretta. Grazie per il commento.
Nawaz,

2
@Destruttore: perché una chiamata di funzione del modulo di f(a,b)richiama una funzione libera . Quindi, nel caso di std::operator<<(std::cout, std::endl);, non esiste una tale funzione libera che assume std::endlcome secondo argomento. È la funzione membro che prende std::endlcome argomento e per la quale devi scrivere std::cout.operator<<(std::endl);. e poiché esiste una funzione libera che assume char const*come secondo argomento, "\n"funziona; '\n'funzionerebbe pure.
Nawaz,

30

Forse è meglio iniziare con il perché, e solo allora andare al come.

Quando sono stati introdotti gli spazi dei nomi, l'idea era quella di avere tutto definito negli spazi dei nomi, in modo che librerie separate non interferissero l'una con l'altra. Tuttavia, ciò ha introdotto un problema con gli operatori. Guarda ad esempio il seguente codice:

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

Naturalmente avresti potuto scrivere N::operator++(x), ma ciò avrebbe sconfitto l'intero punto di sovraccarico dell'operatore. Pertanto è stata trovata una soluzione che ha permesso al compilatore di trovare operator++(X&)nonostante non fosse nell'ambito. D'altra parte, non dovrebbe ancora trovarne un altro operator++definito in un altro spazio dei nomi non correlato che potrebbe rendere la chiamata ambigua (in questo semplice esempio, non otterresti ambiguità, ma in esempi più complessi, potresti). La soluzione era Argument Dependent Lookup (ADL), chiamata in questo modo poiché la ricerca dipende dall'argomento (più precisamente, dal tipo di argomento). Poiché lo schema è stato inventato da Andrew R. Koenig, viene spesso chiamato ricerca Koenig.

Il trucco è che per le chiamate di funzione, oltre alla normale ricerca dei nomi (che trova i nomi nell'ambito nel punto di utilizzo), viene effettuata una seconda ricerca negli ambiti dei tipi di qualsiasi argomento fornito alla funzione. Così, nell'esempio di cui sopra, se si scrive x++nel principale, cerca operator++non solo in ambito globale, ma in aggiunta nel campo di applicazione in cui il tipo di x, N::X, è stato definito, vale a dire in namespace N. E lì trova una corrispondenza operator++, e quindi x++funziona e basta. Un altro operator++definito in un altro spazio dei nomi, ad esempio N2, non sarà trovato, tuttavia. Poiché ADL non è limitato agli spazi dei nomi, è possibile utilizzare anche f(x)anziché N::f(x)in main().


Grazie! Non ho mai capito davvero perché fosse lì!
user965369

20

A mio avviso, non tutto va bene. Le persone, compresi i venditori di compilatori, lo hanno insultato a causa del suo comportamento a volte sfortunato.

ADL è responsabile di un'importante revisione del ciclo for-range in C ++ 11. Per capire perché ADL può talvolta avere effetti indesiderati, si consideri che vengono considerati non solo gli spazi dei nomi in cui sono definiti gli argomenti, ma anche gli argomenti degli argomenti modello degli argomenti, dei tipi di parametro di tipi di funzione / tipi di punta di tipi di puntatore di tali argomenti e così via.

Un esempio usando boost

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

Ciò ha comportato un'ambiguità se l'utente utilizza la libreria boost.range, poiché entrambi std::beginvengono trovati (mediante ADL utilizzando std::vector) e boost::begintrovati (mediante ADL utilizzando boost::shared_ptr).


Mi sono sempre chiesto quali vantaggi ci siano in primo luogo considerare gli argomenti dei template.
Dennis Zickefoose,

È corretto affermare che ADL è consigliato solo agli operatori ed è meglio scrivere esplicitamente gli spazi dei nomi per altre funzioni?
balki

Considera anche gli spazi dei nomi delle classi di argomenti di base? (sarebbe folle se lo fosse, ovviamente).
Alex B,

3
come risolvere? usa std :: begin?
paulm,

2
@paulm Sì, std::begincancella l'ambiguità dello spazio dei nomi.
Nikos,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.