¿Qué es la "Búsqueda dependiente de argumentos" (también conocida como ADL o "Búsqueda de Koenig")?

176

¿Cuáles son algunas buenas explicaciones sobre qué argumento depende de la búsqueda? Muchas personas también lo llaman Koenig Lookup también.

Preferiblemente me gustaría saber:

  • ¿Por qué es algo bueno?
  • ¿Por qué es algo malo?
  • ¿Como funciona?
usuario965369
fuente

Respuestas:

223

La búsqueda de Koenig , o búsqueda dependiente de argumentos , describe cómo el compilador busca en C ++ nombres no calificados.

El estándar C ++ 11 § 3.4.2 / 1 establece:

Cuando la expresión postfix en una llamada de función (5.2.2) es un id no calificado, se pueden buscar otros espacios de nombres no considerados durante la búsqueda no calificada habitual (3.4.1), y en esos espacios de nombres, declaraciones de función amiga del ámbito de nombres ( 11.3) no se puede ver de otra manera visible. Estas modificaciones a la búsqueda dependen de los tipos de argumentos (y para los argumentos de plantilla, el espacio de nombres del argumento de plantilla).

En términos más simples, Nicolai Josuttis afirma 1 :

No tiene que calificar el espacio de nombres para las funciones si uno o más tipos de argumentos están definidos en el espacio de nombres de la función.

Un ejemplo de código simple:

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

MyNamespace::MyClass obj; // global object


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

En el ejemplo anterior, no hay una usingdeclaración ni una usingdirectiva, pero el compilador identifica correctamente el nombre no calificado doSomething()como la función declarada en el espacio MyNamespacede nombres mediante la aplicación de búsqueda de Koenig .

¿Como funciona?

El algoritmo le dice al compilador que no solo mire el alcance local, sino también los espacios de nombres que contienen el tipo de argumento. Por lo tanto, en el código anterior, el compilador encuentra que el objeto obj, que es el argumento de la función doSomething(), pertenece al espacio de nombres MyNamespace. Entonces, mira ese espacio de nombres para ubicar la declaración de doSomething().

¿Cuál es la ventaja de la búsqueda de Koenig?

Como lo demuestra el sencillo ejemplo de código anterior, la búsqueda de Koenig proporciona comodidad y facilidad de uso al programador. Sin la búsqueda de Koenig, habría una sobrecarga en el programador, para especificar repetidamente los nombres completos o, en su lugar, utilizar numerosas usingdeclaraciones.

¿Por qué la crítica de la búsqueda de Koenig?

La excesiva dependencia de la búsqueda de Koenig puede conducir a problemas semánticos y, a veces, pillar desprevenido al programador.

Considere el ejemplo de std::swap, que es un algoritmo de biblioteca estándar para intercambiar dos valores. Con la búsqueda de Koenig, uno debería ser cauteloso al usar este algoritmo porque:

std::swap(obj1,obj2);

puede no mostrar el mismo comportamiento que:

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

Con ADL, qué versión de la swapfunción se llama dependería del espacio de nombres de los argumentos que se le pasan.

Si existe un espacio de nombres Ay A::obj1, A::obj2y A::swap()existe, el segundo ejemplo dará como resultado una llamada a A::swap(), que podría no ser lo que el usuario quería.

Además, si por alguna razón tanto A::swap(A::MyClass&, A::MyClass&)y std::swap(A::MyClass&, A::MyClass&)se definen, a continuación, el primer ejemplo llamará std::swap(A::MyClass&, A::MyClass&)pero el segundo no se compilará porque swap(obj1, obj2)sería ambiguo.

Trivialidades:

¿Por qué se llama "búsqueda de Koenig"?

Porque fue ideado por el ex investigador y programador de AT&T y Bell Labs, Andrew Koenig .

Otras lecturas:


1 La definición de búsqueda de Koenig es como se define en el libro de Josuttis, The C ++ Standard Library: A Tutorial and Reference .

Alok Save
fuente
11
@AlokSave: +1 para la respuesta, pero la trivia no es correcta. Koenig no inventó ADL, como confiesa aquí :)
legends2k
20
El ejemplo en la crítica del algoritmo de Koenig puede considerarse una "característica" de la búsqueda de Koenig tanto como una "estafa". Usar std :: swap () de esta manera es un idioma común: proporcione un 'using std :: swap ()' en caso de que no se proporcione una versión más especializada A :: swap (). Si hay disponible una versión especializada de A :: swap (), normalmente queremos que se llame a esa. Esto proporciona más genérico para la llamada swap (), ya que podemos confiar en la llamada para compilar y trabajar, pero también podemos confiar en la versión más especializada que se utilizará si hay una.
Anthony Hall
66
@anthrond Hay más en esto. En std::swaprealidad, tiene que hacer eso, ya que la única alternativa sería agregar una std::swapfunción de plantilla de especialización explícita para su Aclase. Sin embargo, si su Aclase es una plantilla en sí misma, sería una especialización parcial en lugar de una especialización explícita. Y la especialización parcial de la función de plantilla no está permitida. Agregar sobrecarga de std::swapsería una alternativa, pero está explícitamente prohibido (no puede agregar cosas al stdespacio de nombres). Entonces ADL es la única forma de hacerlo std::swap.
Adam Badura
1
Hubiera esperado ver una mención de operadores sobrecargados bajo "ventaja de la búsqueda de koenig". El ejemplo con std::swap()parece un poco al revés. Esperaría que el problema sea cuando std::swap()se selecciona en lugar de la sobrecarga específica para el tipo A::swap(),. El ejemplo con std::swap(A::MyClass&, A::MyClass&)parece engañoso. ya stdno tendría una sobrecarga específica para un tipo de usuario, no creo que es un gran ejemplo.
Arvid
1
@gsamaras ... y? Todos podemos ver que la función nunca se definió. Su mensaje de error demuestra que funcionó, en realidad, porque está buscando MyNamespace::doSomething, no solo ::doSomething.
Financia la demanda de Mónica el
69

En Koenig Lookup, si se llama a una función sin especificar su espacio de nombres, entonces el nombre de una función también se busca en los espacios de nombres en los que se define el tipo de argumento (s). Es por eso que también se conoce como Búsqueda de nombre dependiente del argumento , en resumen simplemente ADL .

Es por Koenig Lookup, podemos escribir esto:

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

De lo contrario, tendríamos que escribir:

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

lo que realmente es escribir demasiado y el código se ve muy feo!

En otras palabras, en ausencia de Koenig Lookup, incluso un programa Hello World parece complicado.

Nawaz
fuente
12
Ejemplo persuasivo.
Anthony Hall
10
@AdamBadura: Tenga en cuenta que std::coutes un argumento de la función, que es suficiente para habilitar ADL. ¿Lo notaste?
Nawaz
1
@meet: su pregunta necesita una respuesta larga que no se puede proporcionar en este espacio. Por lo tanto, solo puedo aconsejarle que lea sobre temas como: 1) firma de ostream<<(como en lo que toma como argumentos y lo que devuelve). 2) Nombres totalmente calificados (como std::vectoro std::operator<<). 3) Un estudio más detallado de la búsqueda dependiente de argumentos.
Nawaz
2
@WorldSEnder: Sí, tienes razón. La función que puede tomar std::endlcomo argumento, en realidad es una función miembro. De todos modos, si uso en "\n"lugar de std::endl, entonces mi respuesta es correcta. Gracias por el comentario.
Nawaz
2
@ Destructor: Porque una llamada a la función de la forma de f(a,b)invoca una función libre . Entonces, en el caso de std::operator<<(std::cout, std::endl);, no existe tal función libre que tome std::endlcomo segundo argumento. Es la función miembro la que toma std::endlcomo argumento y para la que debe escribir std::cout.operator<<(std::endl);. y dado que hay una función libre que toma char const*como segundo argumento, "\n"funciona; '\n'funcionaría también
Nawaz
30

Tal vez sea mejor comenzar con el por qué, y solo luego ir al cómo.

Cuando se introdujeron los espacios de nombres, la idea era tener todo definido en los espacios de nombres, para que las bibliotecas separadas no interfieran entre sí. Sin embargo, eso introdujo un problema con los operadores. Mire, por ejemplo, el siguiente 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
  ???
}

Por supuesto, podría haber escrito N::operator++(x), pero eso habría derrotado todo el punto de sobrecarga del operador. Por lo tanto, se tuvo que encontrar una solución que permitiera encontrar el compilador a operator++(X&)pesar del hecho de que no estaba dentro del alcance. Por otro lado, todavía no debería encontrar otro operator++definido en otro espacio de nombres no relacionado que pueda hacer que la llamada sea ambigua (en este ejemplo simple, no obtendría ambigüedad, pero en ejemplos más complejos, podría). La solución fue la búsqueda dependiente de argumentos (ADL), llamada así ya que la búsqueda depende del argumento (más exactamente, del tipo de argumento). Dado que el esquema fue inventado por Andrew R. Koenig, a menudo también se lo llama búsqueda de Koenig.

El truco es que para las llamadas a funciones, además de la búsqueda de nombre normal (que encuentra los nombres en el alcance en el punto de uso), se realiza una segunda búsqueda en los ámbitos de los tipos de cualquier argumento dado a la función. Así, en el ejemplo anterior, si se escribe x++en el principal, busca operator++no sólo en el ámbito global, pero, además, en el ámbito en el que el tipo de x, N::X, se definió, es decir, en namespace N. Y allí encuentra una coincidencia operator++y, por x++lo tanto, simplemente funciona. Sin embargo, no se encontrará otro operator++definido en otro espacio de nombres N2. Dado que ADL no está restringido a espacios de nombres, también puede usar en f(x)lugar de N::f(x)in main().

celtschk
fuente
¡Gracias! ¡Nunca entendí por qué estaba allí!
user965369
20

No todo es bueno, en mi opinión. Las personas, incluidos los vendedores de compiladores, lo han insultado debido a su comportamiento a veces desafortunado.

ADL es responsable de una revisión importante del ciclo for-range en C ++ 11. Para entender por qué ADL a veces puede tener efectos no deseados, considere que no solo se consideran los espacios de nombres donde se definen los argumentos, sino también los argumentos de los argumentos de plantilla de los argumentos, de los tipos de parámetros de los tipos de función / tipos de puntas de los tipos de punteros de esos argumentos , y así sucesivamente.

Un ejemplo usando boost

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

Esto resultó en una ambigüedad si el usuario usa la biblioteca boost.range, porque ambos std::beginse encuentran (mediante ADL usando std::vector) y boost::beginse encuentran (mediante ADL usando boost::shared_ptr).

Johannes Schaub - litb
fuente
Siempre me he preguntado qué beneficio hay al considerar argumentos de plantilla en primer lugar.
Dennis Zickefoose
¿Es justo decir que ADL se recomienda solo para operadores y es mejor escribir los espacios de nombres explícitamente para otras funciones?
balki
¿También considera los espacios de nombres de las clases base de argumentos? (eso sería una locura si lo hace, por supuesto).
Alex B
3
¿como arreglar? use std :: begin?
Paul
2
@paulm Sí, std::beginborra la ambigüedad del espacio de nombres.
Nikos