Namensräume
Varianten
Aktionen

Argumentabhängige Namensauflösung

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

Argument-dependent lookup (ADL), auch bekannt als Koenig-Lookup[1], ist die Menge von Regeln für die Suche nach nicht qualifizierten Funktionsnamen in Funktionsaufrufen, einschließlich impliziter Funktionsaufrufe an überladene Operatoren. Diese Funktionsnamen werden in den Namespaces ihrer Argumente zusätzlich zu den Bereichen und Namespaces gesucht, die durch die übliche nicht qualifizierte Namenssuche berücksichtigt werden.

Argument-dependent lookup ermöglicht die Verwendung von Operatoren, die in einem anderen Namespace definiert sind. Beispiel

#include <iostream>
 
int main()
{
    std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
                           // examines std namespace because the left argument is in
                           // std and finds std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "Test\n"); // Same, using function call notation
 
    // However,
    std::cout << endl; // Error: “endl” is not declared in this namespace.
                       // This is not a function call to endl(), so ADL does not apply
 
    endl(std::cout); // OK: this is a function call: ADL examines std namespace
                     // because the argument of endl is in std, and finds std::endl
 
    (endl)(std::cout); // Error: “endl” is not declared in this namespace.
                       // The sub-expression (endl) is not an unqualified-id
}

Inhalt

[bearbeiten] Details

Zuerst wird die argumentabhängige Suche nicht berücksichtigt, wenn die durch die übliche nicht qualifizierte Suche erzeugte Menge von Suchen eine der folgenden enthält:

1) eine Deklaration eines Klassenmembers.
2) eine Deklaration einer Funktion auf Blockebene (die keine using Deklaration ist).
3) eine Deklaration, die keine Funktion oder Funktionstemplate ist (z. B. ein Funktions-Objekt oder eine andere Variable, deren Name mit dem Namen der gesuchten Funktion kollidiert).

Andernfalls wird für jedes Argument in einem Funktionsaufruf dessen Typ untersucht, um die *zugehörigen Mengen von Namespaces und Klassen* zu ermitteln, die der Suche hinzugefügt werden.

1) Für Argumente von grundlegenden Typen ist die zugehörige Menge von Namespaces und Klassen leer.
2) Für Argumente vom Klassentyp (einschließlich Union) besteht die Menge aus
a) Die Klasse selbst.
b) Wenn die Klasse vollständig ist, alle ihre direkten und indirekten Basisklassen.
c) Wenn die Klasse ein Member einer anderen Klasse ist, die Klasse, deren Member sie ist.
d) Die innersten umschließenden Namespaces der Klassen, die der Menge hinzugefügt wurden.
3) Für Argumente, deren Typ eine Klassentemplate-Spezialisierung ist, werden zusätzlich zu den Klassenregeln die folgenden zugehörigen Klassen und Namespaces zur Menge hinzugefügt.
a) Die Typen aller für Typ-Template-Parameter bereitgestellten Template-Argumente (Nicht-Typ-Template-Parameter und Template-Template-Parameter werden übersprungen).
b) Die Namespaces, in denen sich Template-Template-Argumente befinden.
c) Die Klassen, in denen sich Template-Template-Argumente befinden (falls es sich um Klassen-Member-Templates handelt).
4) Für Argumente vom Enumerationstyp wird der innerste umschließende Namespace der Deklaration des Enumerationstyps zur Menge hinzugefügt. Wenn der Enumerationstyp ein Member einer Klasse ist, wird diese Klasse zur Menge hinzugefügt.
5) Für Argumente vom Typ Zeiger auf T oder Zeiger auf ein Array von T wird der Typ T untersucht und seine zugehörige Menge von Klassen und Namespaces zur Menge hinzugefügt.
6) Für Argumente vom Funktionstyp werden die Parametertypen der Funktion und der Rückgabetyp der Funktion untersucht und ihre zugehörige Menge von Klassen und Namespaces zur Menge hinzugefügt.
7) Für Argumente vom Typ Zeiger auf Memberfunktion F einer Klasse X werden die Parametertypen der Funktion, der Rückgabetyp der Funktion und die Klasse X untersucht und ihre zugehörige Menge von Klassen und Namespaces zur Menge hinzugefügt.
8) Für Argumente vom Typ Zeiger auf Datenelement T einer Klasse X werden der Elementtyp und der Typ X beide untersucht und ihre zugehörige Menge von Klassen und Namespaces zur Menge hinzugefügt.
9) Wenn das Argument der Name oder der Adress-Operator-Ausdruck für eine Menge von überladenen Funktionen (oder Funktionstemplate) ist, wird jede Funktion in der Überladungsmenge untersucht und ihre zugehörige Menge von Klassen und Namespaces zur Menge hinzugefügt.
  • Zusätzlich, wenn die Menge von Überladungen durch einen Template-Identifier benannt wird, werden alle seine Typ-Template-Argumente und Template-Template-Argumente (aber keine Nicht-Typ-Template-Argumente) untersucht und ihre zugehörige Menge von Klassen und Namespaces zur Menge hinzugefügt.

Wenn ein Namespace in der zugehörigen Menge von Klassen und Namespaces ein Inline-Namespace ist, wird sein umschließender Namespace ebenfalls zur Menge hinzugefügt.

Wenn ein Namespace in der zugehörigen Menge von Klassen und Namespaces direkt einen Inline-Namespace enthält, wird dieser Inline-Namespace zur Menge hinzugefügt.

(seit C++11)

Nachdem die zugehörige Menge von Klassen und Namespaces bestimmt wurde, werden alle Deklarationen, die in Klassen dieser Menge gefunden wurden, für die weitere ADL-Verarbeitung verworfen, mit Ausnahme von Friend-Funktionen im Namespace-Scope und Funktionstemplate, wie in Punkt 2 unten angegeben.

Die Menge der Deklarationen, die durch die gewöhnliche nicht qualifizierte Suche gefunden werden, und die Menge der Deklarationen, die in allen Elementen der durch ADL erzeugten zugehörigen Menge gefunden werden, werden zusammengeführt, mit folgenden Sonderregeln:

1) using Direktiven in den zugehörigen Namespaces werden ignoriert.
2) Friend-Funktionen im Namespace-Scope (und Funktionstemplate), die in einer zugehörigen Klasse deklariert sind, sind durch ADL sichtbar, auch wenn sie durch normale Suche nicht sichtbar sind.
3) Alle Namen außer Funktionen und Funktionstemplate werden ignoriert (keine Kollision mit Variablen).

[bearbeiten] Anmerkungen

Aufgrund von Argument-dependent lookup gelten Nicht-Member-Funktionen und Nicht-Member-Operatoren, die im selben Namespace wie eine Klasse definiert sind, als Teil der öffentlichen Schnittstelle dieser Klasse (wenn sie durch ADL gefunden werden)[2].

ADL ist der Grund für das etablierte Idiom zum Vertauschen zweier Objekte in generischem Code: using std::swap; swap(obj1, obj2); Da der direkte Aufruf von std::swap(obj1, obj2) keine benutzerdefinierten swap()-Funktionen berücksichtigen würde, die im selben Namespace wie die Typen von obj1 oder obj2 definiert sein könnten, und ein direkter Aufruf des nicht qualifizierten swap(obj1, obj2) nichts aufrufen würde, wenn keine benutzerdefinierte Überladung bereitgestellt wurde. Insbesondere verwenden std::iter_swap und alle anderen Standardbibliotheksalgorithmen diesen Ansatz, wenn sie mit Swappable-Typen arbeiten.

Namenssuchregeln machen es unpraktisch, Operatoren im globalen oder benutzerdefinierten Namespace zu deklarieren, die auf Typen aus dem std-Namespace operieren, z. B. ein benutzerdefinierter operator>> oder operator+ für std::vector oder für std::pair (es sei denn, die Elementtypen des Vektors/Paares sind benutzerdefinierte Typen, die ihren Namespace zu ADL hinzufügen würden). Solche Operatoren würden aus Template-Instanziierungen wie den Standardbibliotheksalgorithmen nicht gefunden werden. Weitere Details finden Sie unter abhängige Namen.

ADL kann eine Friend-Funktion (typischerweise ein überladener Operator) finden, die vollständig innerhalb einer Klasse oder eines Klassentemplates definiert ist, selbst wenn sie nie auf Namespace-Ebene deklariert wurde.

template<typename T>
struct number
{
    number(int);
    friend number gcd(number x, number y) { return 0; }; // Definition within
                                                         // a class template
};
 
// Unless a matching declaration is provided gcd is
// an invisible (except through ADL) member of this namespace
void g()
{
    number<double> a(3), b(4);
    a = gcd(a, b); // Finds gcd because number<double> is an associated class,
                   // making gcd visible in its namespace (global scope)
//  b = gcd(3, 4); // Error; gcd is not visible
}

Obwohl ein Funktionsaufruf durch ADL aufgelöst werden kann, auch wenn die normale Suche nichts findet, erfordert ein Funktionsaufruf an ein Funktionstemplate mit explizit angegebenen Template-Argumenten, dass eine durch normale Suche gefundene Deklaration des Templates existiert (andernfalls ist es ein Syntaxfehler, auf einen unbekannten Namen gefolgt von einem Kleiner-als-Zeichen zu stoßen).

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 non-type 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
}
(bis C++20)

In den folgenden Kontexten findet eine reine ADL-Suche (d. h. Suche nur in zugehörigen Namespaces) statt:

  • die Suche nach Nicht-Member-Funktionen begin und end, die durch die range-for-Schleife durchgeführt wird, wenn die Member-Suche fehlschlägt.
(seit C++11)
(seit C++17)

[bearbeiten] Beispiele

Beispiel von 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); // Calls B::f (endless recursion)
    }
 
    void g(A::X x)
    {
        g(x); // Error: ambiguous between B::g (ordinary lookup)
              //        and A::g (argument-dependent lookup)
    }
 
    void h(A::Y y)
    {
        h(y); // Calls B::h (endless recursion): ADL examines the A namespace
              // but finds no A::h, so only B::h from ordinary lookup is used
    }
}

[bearbeiten] Fehlerberichte

Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR angewendet auf Verhalten wie veröffentlicht Korrigiertes Verhalten
CWG 33 C++98 die zugehörigen Namespaces oder Klassen sind nicht spezifiziert
wenn ein für die Suche verwendetes Argument die Adresse eines
Gruppe von überladenen Funktionen oder eines Funktionstemplate ist
spezifiziert
CWG 90 C++98 die zugehörigen Klassen einer verschachtelten Nicht-Union-Klasse
schlossen ihre umschließende Klasse nicht ein, aber eine verschachtelte
Union war mit ihrer umschließenden Klasse verbunden
Nicht-Unions auch verbunden
CWG 239 C++98 eine Funktionsdeklaration auf Blockebene, die in der normalen
nicht qualifizierten Suche gefunden wurde, verhinderte nicht, dass ADL stattfand
ADL wurde nicht berücksichtigt, außer
für using Deklarationen
CWG 997 C++98 abhängige Parametertypen und Rückgabetypen waren
aus der Betrachtung bei der Bestimmung der zugehörigen
Klassen und Namespaces eines Funktionstemplate ausgeschlossen
berücksichtigt
CWG 1690 C++98
C++11
ADL konnte keine Lambdas (C++11) oder Objekte
von lokalen Klassentypen (C++98) finden, die zurückgegeben werden
sie können gefunden werden
CWG 1691 C++11 ADL hatte überraschende Verhaltensweisen für undurchsichtige Enumerationsdeklarationen fixed
CWG 1692 C++98 doppelt verschachtelte Klassen hatten keine zugehörigen Namespaces
(ihre umschließenden Klassen sind keine Member eines Namespaces)
zugehörige Namespaces sind
erweitert auf die innersten
umschließenden Namespaces
CWG 2857 C++98 die zugehörigen Klassen eines unvollständigen
Klassentyps schlossen ihre Basisklassen ein
nicht eingeschlossen

[bearbeiten] Siehe auch

[bearbeiten] Externe Links

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