Quelles sont les bonnes explications sur la recherche dépendante des arguments? Beaucoup de gens l'appellent également Koenig Lookup.
Je voudrais de préférence savoir:
Recherche Koenig , ou Recherche dépendante de l'argument , décrit comment les noms non qualifiés sont recherchés par le compilateur en C++.
La norme C++ 11 § 3.4.2/1 stipule:
Lorsque l'expression de suffixe dans un appel de fonction (5.2.2) est un identificateur non qualifié, d'autres espaces de noms non pris en compte lors de la recherche habituelle non qualifiée (3.4.1) peuvent être recherchés, et dans ces espaces de noms, les déclarations de fonctions amis de portée d'espace de noms ( 11.3) non visibles autrement peuvent être trouvés. Ces modifications de la recherche dépendent des types d'arguments (et pour les arguments de modèle de modèle, l'espace de noms de l'argument de modèle).
En termes plus simples, Nicolai Josuttis déclare1:
Vous n'avez pas besoin de qualifier l'espace de noms pour les fonctions si un ou plusieurs types d'arguments sont définis dans l'espace de noms de la fonction.
Un exemple de code simple:
namespace MyNamespace
{
class MyClass {};
void doSomething(MyClass);
}
MyNamespace::MyClass obj; // global object
int main()
{
doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}
Dans l'exemple ci-dessus, il n'y a ni déclaration using
- ni directive using
- mais le compilateur identifie toujours correctement le nom non qualifié doSomething()
comme la fonction déclarée dans l'espace de noms MyNamespace
en appliquant Recherche Koenig.
L'algorithme indique au compilateur de ne pas simplement regarder la portée locale, mais aussi les espaces de noms qui contiennent le type de l'argument. Ainsi, dans le code ci-dessus, le compilateur constate que l'objet obj
, qui est l'argument de la fonction doSomething()
, appartient à l'espace de noms MyNamespace
. Ainsi, il regarde cet espace de noms pour localiser la déclaration de doSomething()
.
Comme le montre l'exemple de code simple ci-dessus, la recherche Koenig offre une commodité et une facilité d'utilisation au programmeur. Sans la recherche Koenig, il y aurait un surcoût sur le programmeur, pour spécifier à plusieurs reprises les noms complets, ou à la place, utiliser de nombreuses déclarations using
-.
Une confiance excessive dans la recherche Koenig peut entraîner des problèmes sémantiques et parfois prendre le programmeur au dépourvu.
Prenons l'exemple de std::swap
, qui est un algorithme de bibliothèque standard pour échanger deux valeurs. Avec la recherche Koenig, il faudrait être prudent lors de l'utilisation de cet algorithme car:
std::swap(obj1,obj2);
peut ne pas présenter le même comportement que:
using std::swap;
swap(obj1, obj2);
Avec ADL, la version de la fonction swap
qui sera appelée dépend de l'espace de noms des arguments qui lui sont passés.
S'il existe un espace de noms A
et si A::obj1
, A::obj2
& A::swap()
existent, alors le deuxième exemple entraînera un appel à A::swap()
, ce qui n'est peut-être pas ce que l'utilisateur souhaite.
De plus, si pour une raison quelconque, A::swap(A::MyClass&, A::MyClass&)
et std::swap(A::MyClass&, A::MyClass&)
sont définis, le premier exemple appellera std::swap(A::MyClass&, A::MyClass&)
mais le second ne compilera pas car swap(obj1, obj2)
serait ambigu.
Parce qu'il a été conçu par un ancien chercheur et programmeur d'AT & T et des Bell Labs, Andrew Koenig.
Standard C++ 03/11 [basic.lookup.argdep]: 3.4.2 Recherche de nom dépendante de l'argument.
1 La définition de la recherche Koenig est celle définie dans le livre de Josuttis, La bibliothèque standard C++: un tutoriel et une référence.
Dans Koenig Lookup, si une fonction est appelée sans spécifier son espace de noms, le nom d'une fonction est également recherché dans les espaces de noms dans lesquels le type des arguments est définie. C'est pourquoi il est également connu sous le nom de Recherche de nom dépendant de l'argument , en bref simplement ADL .
C'est à cause de Koenig Lookup, nous pouvons écrire ceci:
std::cout << "Hello World!" << "\n";
Sinon, il faudrait écrire:
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
ce qui est vraiment trop tapant et le code a l'air vraiment moche!
En d'autres termes, en l'absence de Koenig Lookup, même un programme Hello World semble compliqué.
Il est peut-être préférable de commencer par le pourquoi, puis de passer au comment.
Lorsque les espaces de noms ont été introduits, l'idée était de tout définir dans des espaces de noms, afin que des bibliothèques distinctes n'interfèrent pas entre elles. Cependant, cela a introduit un problème avec les opérateurs. Regardez par exemple le code suivant:
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
???
}
Bien sûr, vous auriez pu écrire N::operator++(x)
, mais cela aurait déjoué tout le point de surcharge de l'opérateur. Par conséquent, une solution devait être trouvée qui permettait au compilateur de trouver operator++(X&)
malgré le fait qu'elle n'était pas dans la portée. D'un autre côté, il ne devrait toujours pas trouver un autre operator++
Défini dans un autre espace de noms non lié qui pourrait rendre l'appel ambigu (dans cet exemple simple, vous n'obtiendrez pas d'ambiguïté, mais dans des exemples plus complexes, vous pourriez ). La solution était la recherche dépendante de l'argument (ADL), appelée ainsi car la recherche dépend de l'argument (plus exactement, du type de l'argument). Puisque le schéma a été inventé par Andrew R. Koenig, il est aussi souvent appelé recherche Koenig.
L'astuce est que pour les appels de fonction, en plus de la recherche de nom normale (qui trouve les noms dans la portée au point d'utilisation), une deuxième recherche est effectuée dans les portées des types d'arguments donnés à la fonction. Ainsi, dans l'exemple ci-dessus, si vous écrivez x++
Dans main, il recherche operator++
Non seulement dans la portée globale, mais également dans la portée où le type de x
, N::X
, A été défini, c'est-à-dire dans namespace N
. Et là, il trouve un operator++
Correspondant, et donc x++
Fonctionne. Un autre operator++
Défini dans un autre espace de noms, disons N2
, Ne sera cependant pas trouvé. Dans la mesure où ADL n'est pas limité aux espaces de noms, vous pouvez également utiliser f(x)
au lieu de N::f(x)
dans main()
.
Tout n'est pas bon à mon avis. Les gens, y compris les fournisseurs de compilateurs, l'ont insulté en raison de son comportement parfois malheureux.
ADL est responsable d'une refonte majeure de la boucle for-range en C++ 11. Pour comprendre pourquoi ADL peut parfois avoir des effets inattendus, considérez que non seulement les espaces de noms où les arguments sont définis sont pris en compte, mais aussi les arguments des arguments de modèle des arguments, des types de paramètres des types de fonctions/types pointes des types de pointeurs de ces arguments et ainsi de suite.
Un exemple utilisant boost
std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);
Il en résulte une ambiguïté si l'utilisateur utilise la bibliothèque boost.range, car les deux std::begin
est trouvé (par ADL en utilisant std::vector
) et boost::begin
est trouvé (par ADL en utilisant boost::shared_ptr
).