O que é “pesquisa dependente de argumentos” (também conhecido como ADL ou “Koenig Lookup”)?

Quais são algumas boas explicações sobre qual pesquisa dependente de argumento é? Muitas pessoas também chamam isso de Koenig Lookup.

De preferência eu gostaria de saber:

  • Por que isso é bom?
  • Por que isso é ruim?
  • Como funciona?

Koenig Lookup , ou Consulta Dependente de Argumento , descreve como os nomes não qualificados são procurados pelo compilador em C ++.

O padrão C ++ 11 § 3.4.2 / 1 declara:

Quando a expressão de postfix em uma chamada de function (5.2.2) é um id não qualificado, outros espaços de nomes não considerados durante a consulta não qualificada (3.4.1) podem ser pesquisados ​​e, nesses namespaces, declarações de function de amigo de escopo namespace ( 11.3) de outra forma não visível pode ser encontrado. Essas modificações na pesquisa dependem dos tipos de argumentos (e para argumentos de gabaritos de modelo, o namespace do argumento de gabarito).

Em termos mais simples, Nicolai Josuttis declara 1 :

Você não precisa qualificar o namespace para funções se um ou mais tipos de argumentos forem definidos no namespace da function.

Um exemplo de código simples:

 namespace MyNamespace { class MyClass {}; void doSomething(MyClass); } MyNamespace::MyClass obj; // global object int main() { doSomething(obj); // Works Fine - MyNamespace::doSomething() is called. } 

No exemplo acima, não há o using -declaration nem o using -directive, mas ainda assim o compilador identifica corretamente o nome não qualificado doSomething() como a function declarada no namespace MyNamespace aplicando a pesquisa Koenig .

Como funciona?

O algoritmo diz ao compilador para não apenas olhar para o escopo local, mas também os namespaces que contêm o tipo do argumento. Assim, no código acima, o compilador descobre que o object obj , que é o argumento da function doSomething() , pertence ao namespace MyNamespace . Então, ele olha para aquele namespace para localizar a declaração do doSomething() .

Qual é a vantagem da pesquisa Koenig?

Como o exemplo de código simples acima demonstra, a pesquisa Koenig fornece conveniência e facilidade de uso para o programador. Sem a pesquisa Koenig, haveria uma sobrecarga no programador, para especificar repetidamente os nomes completos, ou, em vez disso, usar numerosos using -declarações.

Por que as críticas da pesquisa Koenig?

A confiança excessiva na pesquisa Koenig pode levar a problemas semânticos, e pegar o programador de surpresa às vezes.

Considere o exemplo de std::swap , que é um algoritmo de biblioteca padrão para trocar dois valores. Com a pesquisa Koenig, é preciso ter caucanvas ao usar esse algoritmo porque:

 std::swap(obj1,obj2); 

pode não mostrar o mesmo comportamento de:

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

Com o ADL, qual versão da function swap é chamada dependerá do namespace dos argumentos passados ​​para ela.

Se existir um namespace A e se A::obj1 , A::obj2 & A::swap() existir, o segundo exemplo resultará em uma chamada para A::swap() , que pode não ser o que o usuário queria .

Além disso, se por algum motivo tanto A::swap(A::MyClass&, A::MyClass&) e std::swap(A::MyClass&, A::MyClass&) são definidos, então o primeiro exemplo irá chamar std::swap(A::MyClass&, A::MyClass&) mas o segundo não irá compilar porque swap(obj1, obj2) seria ambíguo.

Curiosidades:

Por que é chamado de “Koenig lookup”?

Porque foi inventado pelo ex-pesquisador e programador da AT & T e Bell Labs, Andrew Koenig .

Leitura adicional:

  • Pesquisa de nome de Herb Sutter em GotW

  • Padrão C ++ 03/11 [basic.lookup.argdep]: 3.4.2 Pesquisa de nome dependente do argumento.


1 A definição de pesquisa Koenig é definida no livro de Josuttis, The C ++ Standard Library: Um tutorial e uma referência .

No Koenig Lookup, se uma function é chamada sem especificar seu namespace, então o nome de uma function também é pesquisado no (s) namespace (s) no (s) qual (is) o (s) tipo (s) do (s) argumento (s). É por isso que também é conhecido como Lookup de Nome Dependente de Argumento , em resumo simplesmente ADL .

É por causa do Koenig Lookup, podemos escrever isso:

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

Caso contrário, teríamos que escrever:

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

o que realmente é muita digitação e o código parece realmente feio!

Em outras palavras, na ausência do Koenig Lookup, até mesmo um programa Hello World parece complicado.

Talvez seja melhor começar com o porquê e só então ir para o como.

Quando os namespaces foram introduzidos, a ideia era ter tudo definido nos namespaces, de modo que as bibliotecas separadas não interferissem umas com as outras. No entanto, isso introduziu um problema com os operadores. Procure por exemplo no seguinte código:

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

Claro que você poderia ter escrito N::operator++(x) , mas isso teria derrotado todo o ponto de sobrecarga do operador. Portanto, foi preciso encontrar uma solução que permitisse ao compilador encontrar o operator++(X&) apesar de não estar no escopo. Por outro lado, ainda não deve encontrar outro operator++ definido em outro espaço de nomes não relacionado que possa tornar a chamada ambígua (nesse exemplo simples, você não obteria ambigüidade, mas em exemplos mais complexos, você poderia). A solução era o ADL (Argument Dependent Lookup), chamado dessa maneira, pois a pesquisa depende do argumento (mais exatamente, do tipo do argumento). Desde que o esquema foi inventado por Andrew R. Koenig, também é chamado de pesquisa Koenig.

O truque é que, para chamadas de function, além da pesquisa de nome normal (que encontra nomes no escopo no ponto de uso), é feita uma segunda pesquisa nos escopos dos tipos de argumentos fornecidos para a function. Portanto, no exemplo acima, se você escrever x++ em main, ele procura o operator++ não apenas no escopo global, mas também no escopo onde o tipo de x , N::X , foi definido, ou seja, no namespace N E lá ele encontra um operator++ correspondência operator++ e, portanto, o x++ apenas funciona. Outro operator++ definido em outro namespace, digamos N2 , não será encontrado, no entanto. Como o ADL não está restrito a namespaces, você também pode usar f(x) invés de N::f(x) em main() .

Nem tudo é bom, na minha opinião. Pessoas, incluindo vendedores de compiladores, têm insultado isso por causa de seu comportamento às vezes infeliz.

O ADL é responsável por uma grande revisão do loop for-range no C ++ 11. Para entender por que o ADL às vezes pode ter efeitos não intencionais, considere que não apenas os namespaces em que os argumentos são definidos são considerados, mas também os argumentos dos argumentos de modelo dos argumentos, dos tipos de parâmetro dos tipos de function / tipos pointee dos tipos de ponteiro desses argumentos e assim por diante.

Um exemplo usando boost

 std::vector> v; auto x = begin(v); 

Isso resultou em uma ambigüidade se o usuário usa a biblioteca boost.range, porque ambos std::begin é encontrado (por ADL usando std::vector ) e boost::begin é encontrado (por ADL usando boost::shared_ptr ).