Argument-dependent lookup
La recherche dépendante des arguments (ADL), également connue sous le nom de recherche de Koenig [1] , est l'ensemble des règles pour rechercher les noms de fonctions non qualifiées dans les expressions d'appel de fonction , y compris les appels de fonction implicites aux opérateurs surchargés . Ces noms de fonctions sont recherchés dans les espaces de noms de leurs arguments en plus des portées et espaces de noms considérés par la recherche de nom non qualifié usuelle.
La recherche dépendante des arguments permet d'utiliser des opérateurs définis dans un espace de noms différent. Exemple :
#include <iostream> int main() { std::cout << "Test\n"; // Il n'y a pas d'opérateur<< dans l'espace de noms global, mais ADL // examine l'espace de noms std car l'argument gauche est dans // std et trouve std::operator<<(std::ostream&, const char*) operator<<(std::cout, "Test\n"); // Idem, en utilisant la notation d'appel de fonction // Cependant, std::cout << endl; // Erreur : "endl" n'est pas déclaré dans cet espace de noms. // Ce n'est pas un appel de fonction à endl(), donc ADL ne s'applique pas endl(std::cout); // OK : c'est un appel de fonction : ADL examine l'espace de noms std // car l'argument de endl est dans std, et trouve std::endl (endl)(std::cout); // Erreur : "endl" n'est pas déclaré dans cet espace de noms. // La sous-expression (endl) n'est pas un unqualified-id }
Table des matières |
Détails
Premièrement, la recherche dépendante des arguments n'est pas considérée si l'ensemble de recherche produit par la recherche non qualifiée habituelle contient l'un des éléments suivants :
Sinon, pour chaque argument dans une expression d'appel de fonction, son type est examiné pour déterminer l'ensemble associé de namespaces et de classes qu'il ajoutera à la recherche.
T
ou pointeur vers un tableau de
T
, le type
T
est examiné et son ensemble associé de classes et d'espaces de noms est ajouté à l'ensemble.
F
de la classe
X
, les types des paramètres de la fonction, le type de retour de la fonction, et la classe
X
sont examinés et leurs ensembles associés de classes et d'espaces de noms sont ajoutés à l'ensemble.
T
de la classe
X
, le type membre et le type
X
sont tous deux examinés et leur ensemble associé de classes et d'espaces de noms est ajouté à l'ensemble.
- De plus, si l'ensemble de surcharges est désigné par un identifiant de modèle , tous ses arguments de type de modèle et arguments de modèle de modèle (mais pas les arguments de modèle constants) sont examinés et leur ensemble associé de classes et d'espaces de noms est ajouté à l'ensemble.
|
Si un espace de noms quelconque dans l'ensemble associé de classes et d'espaces de noms est un espace de noms inline , son espace de noms englobant est également ajouté à l'ensemble. Si un espace de noms quelconque dans l'ensemble associé de classes et d'espaces de noms contient directement un espace de noms inline, cet espace de noms inline est ajouté à l'ensemble. |
(depuis C++11) |
Une fois l'ensemble associé de classes et d'espaces de noms déterminé, toutes les déclarations trouvées dans les classes de cet ensemble sont écartées pour la suite du traitement ADL, à l'exception des fonctions friend et des modèles de fonction définis au niveau de l'espace de noms, comme indiqué au point 2 ci-dessous.
L'ensemble des déclarations trouvées par la recherche non qualifiée ordinaire et l'ensemble des déclarations trouvées dans tous les éléments de l'ensemble associé produit par ADL, sont fusionnés, avec les règles spéciales suivantes :
Notes
En raison de la recherche dépendante de l'argument, les fonctions non-membres et les opérateurs non-membres définis dans le même espace de noms qu'une classe sont considérés comme faisant partie de l'interface publique de cette classe (s'ils sont trouvés via ADL) [2] .
L'ADL est la raison derrière l'idiome établi pour échanger deux objets dans le code générique :
using
std::
swap
;
swap
(
obj1, obj2
)
;
car appeler
std::
swap
(
obj1, obj2
)
directement ne prendrait pas en compte les fonctions
swap()
définies par l'utilisateur qui pourraient être définies dans le même espace de noms que les types de
obj1
ou
obj2
, et simplement appeler
swap
(
obj1, obj2
)
sans qualification n'appellerait rien si aucune surcharge définie par l'utilisateur n'était fournie. En particulier,
std::iter_swap
et tous les autres algorithmes de la bibliothèque standard utilisent cette approche lorsqu'ils traitent des types
Swappable
.
Les règles de recherche de nom rendent peu pratique la déclaration d'opérateurs dans l'espace de noms global ou défini par l'utilisateur qui opèrent sur des types du
std
namespace, par exemple un
operator
>>
ou
operator
+
personnalisé pour
std::vector
ou pour
std::pair
(sauf si les types d'éléments du vector/pair sont des types définis par l'utilisateur, ce qui ajouterait leur espace de noms à l'ADL). De tels opérateurs ne seraient pas trouvés lors des instanciations de templates, comme dans les algorithmes de la bibliothèque standard. Voir
les noms dépendants
pour plus de détails.
L'ADL peut trouver une friend function (généralement, un opérateur surchargé) qui est définie entièrement dans une classe ou un modèle de classe, même si elle n'a jamais été déclarée au niveau de l'espace de noms.
template<typename T> struct number { number(int); friend number gcd(number x, number y) { return 0; }; // Définition à l'intérieur // d'un modèle de classe }; // À moins qu'une déclaration correspondante soit fournie, gcd est // un membre invisible (sauf via ADL) de cet espace de noms void g() { number<double> a(3), b(4); a = gcd(a, b); // Trouve gcd car number<double> est une classe associée, // rendant gcd visible dans son espace de noms (portée globale) // b = gcd(3, 4); // Erreur ; gcd n'est pas visible }
|
Bien qu'un appel de fonction puisse être résolu via ADL même si la recherche ordinaire ne trouve rien, un appel de fonction vers une fonction template avec des arguments template explicitement spécifiés nécessite qu'il y ait une déclaration du template trouvée par recherche ordinaire (sinon, c'est une erreur de syntaxe de rencontrer un nom inconnu suivi d'un caractère inférieur à). 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 } |
(jusqu'en C++20) |
Dans les contextes suivants, une recherche ADL uniquement (c'est-à-dire une recherche dans les espaces de noms associés uniquement) a lieu :
|
(depuis C++11) |
- la recherche de noms dépendants à partir du point d'instanciation du modèle.
|
(depuis C++17) |
Exemples
|
Cette section est incomplète
Raison : plus d'exemples |
Exemple tiré 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); // Appelle B::f (récursion infinie) } void g(A::X x) { g(x); // Erreur : ambiguïté entre B::g (recherche ordinaire) // et A::g (recherche dépendante des arguments) } void h(A::Y y) { h(y); // Appelle B::h (récursion infinie) : ADL examine l'espace de noms A // mais ne trouve pas A::h, donc seul B::h de la recherche ordinaire est utilisé } }
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Applicable à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 33 | C++98 |
les espaces de noms ou classes associés n'étaient pas spécifiés
si un argument utilisé pour la recherche était l'adresse d'un groupe de fonctions surchargées ou d'un modèle de fonction |
spécifié |
| CWG 90 | C++98 |
les classes associées d'une classe non-union imbriquée
n'incluaient pas sa classe englobante, mais une union imbriquée était associée à sa classe englobante |
les non-unions également associées |
| CWG 239 | C++98 |
une déclaration de fonction de portée de bloc trouvée dans la recherche
non qualifiée ordinaire n'empêchait pas la ADL de se produire |
ADL non considérée sauf
pour les déclarations using |
| CWG 997 | C++98 |
les types de paramètres dépendants et les types de retour étaient
exclus de la considération pour déterminer les classes et espaces de noms associés d'un modèle de fonction |
inclus |
| CWG 1690 |
C++98
C++11 |
ADL ne pouvait pas trouver les lambdas (C++11) ou les objets
de types de classe locale (C++98) qui sont retournés |
ils peuvent être trouvés |
| CWG 1691 | C++11 | ADL avait des comportements surprenants pour les déclarations opaques d'énumération | corrigé |
| CWG 1692 | C++98 |
les classes doublement imbriquées n'avaient pas d'espaces de noms associés
(leurs classes englobantes ne sont membres d'aucun espace de noms) |
les espaces de noms associés sont
étendus aux espaces de noms englobants les plus internes |
| CWG 2857 | C++98 |
les classes associées d'un type de classe
incomplet incluaient ses classes de base |
non incluses |
Voir aussi
Liens externes
|