Namespaces
Variants

Argument-dependent lookup

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

La búsqueda dependiente de argumentos (ADL), también conocida como búsqueda de Koenig [1] , es el conjunto de reglas para buscar nombres de funciones no calificados en expresiones de llamada a funciones , incluyendo llamadas implícitas a funciones para operadores sobrecargados . Estos nombres de función se buscan en los espacios de nombres de sus argumentos además de los ámbitos y espacios de nombres considerados por la habitual búsqueda de nombres no calificados .

La búsqueda dependiente de argumentos hace posible utilizar operadores definidos en un espacio de nombres diferente. Ejemplo:

#include <iostream>
int main()
{
    std::cout << "Test\n"; // No hay operator<< en el espacio de nombres global, pero ADL
                           // examina el espacio de nombres std porque el argumento izquierdo está en
                           // std y encuentra std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "Test\n"); // Lo mismo, usando notación de llamada a función
    // Sin embargo,
    std::cout << endl; // Error: “endl” no está declarado en este espacio de nombres.
                       // Esto no es una llamada a función endl(), por lo que ADL no aplica
    endl(std::cout); // OK: esto es una llamada a función: ADL examina el espacio de nombres std
                     // porque el argumento de endl está en std, y encuentra std::endl
    (endl)(std::cout); // Error: “endl” no está declarado en este espacio de nombres.
                       // La sub-expresión (endl) no es un unqualified-id
}

Contenidos

Detalles

Primero, la búsqueda dependiente de argumento no se considera si el conjunto de búsqueda producido por la búsqueda no calificada usual contiene cualquiera de los siguientes:

1) una declaración de un miembro de clase.
2) una declaración de una función en el ámbito de bloque (que no sea una using declaration ).
3) cualquier declaración que no sea una función o una plantilla de función (por ejemplo, un objeto función u otra variable cuyo nombre entre en conflicto con el nombre de la función que se está buscando).

De lo contrario, para cada argumento en una expresión de llamada de función se examina su tipo para determinar el conjunto asociado de espacios de nombres y clases que añadirá a la búsqueda.

1) Para argumentos de tipo fundamental, el conjunto asociado de espacios de nombres y clases está vacío.
2) Para argumentos de tipo clase (incluyendo union), el conjunto consiste en:
a) La clase en sí.
b) Si la clase es completa , todas sus clases base directas e indirectas.
c) Si la clase es un miembro de otra clase , la clase de la cual es miembro.
d) Los espacios de nombres envolventes más internos de las clases añadidas al conjunto.
3) Para argumentos cuyo tipo es una class template specialization, además de las reglas de clase, se añaden al conjunto las siguientes clases y espacios de nombres asociados.
a) Los tipos de todos los argumentos de plantilla proporcionados para parámetros de plantilla de tipo (omitiendo parámetros de plantilla constantes y omitiendo parámetros de plantilla de plantilla).
b) Los espacios de nombres en los que son miembros los argumentos de plantilla de plantilla.
c) Las clases en las que cualquier argumento de plantilla de plantilla son miembros (si resultan ser plantillas de miembros de clase).
4) Para argumentos de tipo enumeración, se añade al conjunto el espacio de nombres que encierra más internamente la declaración del tipo de enumeración. Si el tipo de enumeración es miembro de una clase, esa clase se añade al conjunto.
5) Para argumentos de tipo puntero a T o puntero a un arreglo de T , el tipo T es examinado y su conjunto asociado de clases y espacios de nombres es añadido al conjunto.
6) Para argumentos de tipo función, se examinan los tipos de parámetros de la función y el tipo de retorno de la función, y sus conjuntos asociados de clases y namespaces se añaden al conjunto.
7) Para argumentos de tipo puntero a función miembro F de la clase X , los tipos de parámetros de la función, el tipo de retorno de la función, y la clase X son examinados y sus conjuntos asociados de clases y espacios de nombres son añadidos al conjunto.
8) Para argumentos de tipo puntero a miembro de datos T de la clase X , tanto el tipo miembro como el tipo X son examinados y su conjunto asociado de clases y espacios de nombres se añade al conjunto.
9) Si el argumento es el nombre o la expresión de dirección-de para un conjunto de funciones sobrecargadas (o plantillas de función), cada función en el conjunto sobrecargado es examinada y su conjunto asociado de clases y espacios de nombres es añadido al conjunto.
  • Adicionalmente, si el conjunto de sobrecargas es nombrado por un identificador de plantilla , todos sus argumentos de plantilla de tipo y argumentos de plantilla de plantilla (pero no argumentos de plantilla constantes) son examinados y su conjunto asociado de clases y espacios de nombres son añadidos al conjunto.

Si cualquier espacio de nombres en el conjunto asociado de clases y espacios de nombres es un espacio de nombres en línea , su espacio de nombres contenedor también se añade al conjunto.

Si cualquier espacio de nombres en el conjunto asociado de clases y espacios de nombres contiene directamente un espacio de nombres en línea, ese espacio de nombres en línea se añade al conjunto.

(desde C++11)

Después de que se determina el conjunto asociado de clases y espacios de nombres, todas las declaraciones encontradas en las clases de este conjunto se descartan para el propósito del procesamiento adicional de ADL, excepto las funciones friend y plantillas de función con ámbito de espacio de nombres, como se establece en el punto 2 a continuación.

El conjunto de declaraciones encontradas mediante la búsqueda no calificada ordinaria unqualified lookup y el conjunto de declaraciones encontradas en todos los elementos del conjunto asociado producido por ADL, se combinan, con las siguientes reglas especiales:

1) using directives se ignoran en los espacios de nombres asociados.
2) Las funciones friend (y plantillas de función) con ámbito de namespace que se declaran en una clase asociada son visibles mediante ADL incluso si no son visibles mediante búsqueda ordinaria.
3) todos los nombres excepto las funciones y plantillas de funciones se ignoran (sin colisión con variables).

Notas

Debido a la búsqueda dependiente de argumentos, las funciones no miembro y los operadores no miembro definidos en el mismo espacio de nombres que una clase se consideran parte de la interfaz pública de esa clase (si se encuentran mediante ADL) [2] .

ADL es la razón detrás del patrón establecido para intercambiar dos objetos en código genérico: using std:: swap ; swap ( obj1, obj2 ) ; porque llamar directamente std:: swap ( obj1, obj2 ) no consideraría las funciones swap() definidas por el usuario que podrían estar definidas en el mismo espacio de nombres que los tipos de obj1 o obj2 , y simplemente llamar swap ( obj1, obj2 ) no llamaría a nada si no se proporcionara ninguna sobrecarga definida por el usuario. En particular, std::iter_swap y todos los demás algoritmos de la biblioteca estándar utilizan este enfoque cuando tratan con tipos Swappable .

Las reglas de búsqueda de nombres hacen que sea poco práctico declarar operadores en el espacio de nombres global o definido por el usuario que operen en tipos del espacio de nombres std , por ejemplo, un operator >> o operator + personalizado para std::vector o para std::pair (a menos que los tipos de elemento del vector/par sean tipos definidos por el usuario, lo que agregaría su espacio de nombres a ADL). Dichos operadores no serían encontrados en las instanciaciones de plantillas, como los algoritmos de la biblioteca estándar. Consulte nombres dependientes para más detalles.

ADL puede encontrar una friend function (típicamente, un operador sobrecargado) que está definida completamente dentro de una clase o plantilla de clase, incluso si nunca fue declarada a nivel de namespace.

template<typename T>
struct number
{
    number(int);
    friend number gcd(number x, number y) { return 0; }; // Definición dentro de
                                                         // una plantilla de clase
};
// A menos que se proporcione una declaración coincidente, gcd es
// un miembro invisible (excepto mediante ADL) de este espacio de nombres
void g()
{
    number<double> a(3), b(4);
    a = gcd(a, b); // Encuentra gcd porque number<double> es una clase asociada,
                   // haciendo gcd visible en su espacio de nombres (ámbito global)
//  b = gcd(3, 4); // Error; gcd no es visible
}

Aunque una llamada a función puede resolverse mediante ADL incluso si la búsqueda ordinaria no encuentra nada, una llamada a función a una plantilla de función con argumentos de plantilla explícitamente especificados requiere que haya una declaración de la plantilla encontrada por búsqueda ordinaria (de lo contrario, es un error de sintaxis encontrar un nombre desconocido seguido de un carácter menor-que).

namespace N1
{
    struct S {};
    template<int X>
    void f(S);
}
namespace N2
{
    template<class T>
    void f(T t);
}
void g(N1::S s)
{
    f<3>(s);     // Syntax error until C++20 (unqualified lookup finds no f)
    N1::f<3>(s); // OK, qualified lookup finds the template 'f'
    N2::f<3>(s); // Error: N2::f does not take a constant parameter
                 //        N1::f is not looked up because ADL only works
                 //              with unqualified names
    using N2::f;
    f<3>(s); // OK: Unqualified lookup now finds N2::f
             //     then ADL kicks in because this name is unqualified
             //     and finds N1::f
}
(hasta C++20)

En los siguientes contextos se produce una búsqueda solo por ADL (es decir, búsqueda únicamente en espacios de nombres asociados):

  • la búsqueda de funciones no miembro begin y end realizada por el bucle range-for si la búsqueda de miembro falla.
(desde C++11)
(desde C++17)

Ejemplos

Ejemplo de http://www.gotw.ca/gotw/030.htm

namespace A
{
    struct X;
    struct Y;
    void f(int);
    void g(X);
}
namespace B
{
    void f(int i)
    {
        f(i); // Llama a B::f (recursión infinita)
    }
    void g(A::X x)
    {
        g(x); // Error: ambigüedad entre B::g (búsqueda ordinaria)
              //        y A::g (búsqueda dependiente de argumento)
    }
    void h(A::Y y)
    {
        h(y); // Llama a B::h (recursión infinita): ADL examina el espacio de nombres A
              // pero no encuentra A::h, por lo que solo se usa B::h de la búsqueda ordinaria
    }
}

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 33 C++98 los espacios de nombres o clases asociados no estaban especificados
si un argumento utilizado para la búsqueda es la dirección de un
grupo de funciones sobrecargadas o una plantilla de función
especificado
CWG 90 C++98 las clases asociadas de una clase anidada no-unión
no incluían su clase envolvente, pero una unión
anidada estaba asociada con su clase envolvente
las no-uniones también asociadas
CWG 239 C++98 una declaración de función en ámbito de bloque encontrada en la
búsqueda no calificada ordinaria no prevenía que ocurriera ADL
ADL no se considera excepto
para declaraciones using
CWG 997 C++98 los tipos de parámetros dependientes y tipos de retorno estaban
excluidos de la consideración al determinar las clases
y espacios de nombres asociados de una plantilla de función
incluidos
CWG 1690 C++98
C++11
ADL no podía encontrar lambdas (C++11) u objetos
de tipos de clase local (C++98) que son retornados
pueden ser encontrados
CWG 1691 C++11 ADL tenía comportamientos sorprendentes para declaraciones opacas de enumeración corregido
CWG 1692 C++98 las clases doblemente anidadas no tenían espacios de nombres asociados
(sus clases envolventes no son miembros de ningún espacio de nombres)
los espacios de nombres asociados son
extendidos a los espacios de nombres
envolventes más internos
CWG 2857 C++98 las clases asociadas de un tipo de clase
incompleto incluían sus clases base
no incluidas

Véase también

Enlaces externos

  1. Andrew Koenig: "A Personal Note About Argument-Dependent Lookup"
  2. H. Sutter (1998) "What's In a Class? - The Interface Principle" en C++ Report, 10(3)