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 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 :

1) une déclaration d'un membre de classe.
2) une déclaration d'une fonction au niveau de la portée de bloc (qui n'est pas une using declaration ).
3) toute déclaration qui n'est pas une fonction ou un modèle de fonction (par exemple, un objet fonction ou une autre variable dont le nom entre en conflit avec le nom de la fonction recherchée).

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.

1) Pour les arguments de type fondamental, l'ensemble associé d'espaces de noms et de classes est vide.
2) Pour les arguments de type classe (y compris union), l'ensemble se compose de :
a) La classe elle-même.
b) Si la classe est complète , toutes ses classes de base directes et indirectes.
c) Si la classe est un membre d'une autre classe , la classe dont elle est membre.
d) Les espaces de noms englobants les plus internes des classes ajoutées à l'ensemble.
3) Pour les arguments dont le type est une spécialisation de modèle de classe , en plus des règles de classe, les classes et espaces de noms associés suivants sont ajoutés à l'ensemble.
a) Les types de tous les arguments template fournis pour les paramètres template de type (en ignorant les paramètres template constants et en ignorant les paramètres template template).
b) Les espaces de noms dans lesquels les arguments de modèle de modèle sont membres.
c) Les classes dans lesquelles les arguments template template sont membres (s'il s'agit de modèles de membres de classe).
4) Pour les arguments de type énumération, l'espace de noms englobant le plus intérieur où le type énumération est déclaré est ajouté à l'ensemble. Si le type énumération est membre d'une classe, cette classe est ajoutée à l'ensemble.
5) Pour les arguments de type pointeur vers 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.
6) Pour les arguments de type fonction, les types des paramètres de la fonction et le type de retour de la fonction sont examinés et leurs ensembles associés de classes et d'espaces de noms sont ajoutés à l'ensemble.
7) Pour les arguments de type pointeur vers une fonction membre 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.
8) Pour les arguments de type pointeur vers membre de données 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.
9) Si l'argument est le nom ou l' expression d'adresse pour un ensemble de fonctions surchargées (ou de modèles de fonction), chaque fonction dans l'ensemble surchargé est examinée et son 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 :

1) using directives dans les espaces de noms associés sont ignorés.
2) Les fonctions friend (et modèles de fonctions) définies dans l'espace de noms et déclarées dans une classe associée sont visibles via ADL même si elles ne sont pas visibles via la recherche ordinaire.
3) tous les noms, à l'exception des fonctions et des modèles de fonctions, sont ignorés (aucune collision avec les variables).

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 :

  • la recherche des fonctions non-membres begin et end effectuée par la boucle range-for si la recherche de membres échoue.
(depuis C++11)
(depuis C++17)

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

  1. Andrew Koenig : "Une Note Personnelle sur la Recherche Dépendante des Arguments"
  2. H. Sutter (1998) "Qu'y a-t-il dans une Classe ? - Le Principe d'Interface" dans C++ Report, 10(3)