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

Argument-dependent lookup (ADL), auch bekannt als Koenig lookup [1] , ist der Satz von Regeln für die Suche nach unqualifizierten Funktionsnamen in Funktionsaufrufausdrücken , einschließlich impliziter Funktionsaufrufe an überladene Operatoren . Diese Funktionsnamen werden zusätzlich zu den Geltungsbereichen und Namensräumen, die durch die übliche unqualifizierte Namenssuche berücksichtigt werden, in den Namensräumen ihrer Argumente gesucht.

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
}

Inhaltsverzeichnis

Details

Zunächst wird das argumentabhängige Lookup nicht berücksichtigt, falls die durch das übliche unqualified lookup erzeugte Lookup-Menge eines der folgenden Elemente enthält:

1) eine Deklaration eines Klassenmembers.
2) eine Deklaration einer Funktion auf Blockebene (die keine using declaration ist).
3) jede Deklaration, die keine Funktion oder Funktionsvorlage ist (z.B. ein Funktionsobjekt oder eine andere Variable, deren Name mit dem Namen der gesuchten Funktion in Konflikt steht).

Andernfalls wird für jedes Argument in einem Funktionsaufrufausdruck dessen Typ untersucht, um den zugehörigen Satz von Namensräumen und Klassen zu bestimmen, der zur Suche hinzugefügt wird.

1) Für Argumente von grundlegenden Typen ist der zugehörige Satz von Namensräumen 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 Mitglied einer anderen Klasse ist, die Klasse, deren Mitglied sie ist.
d) Die innersten umschließenden Namensräume der Klassen, die zur Menge hinzugefügt wurden.
3) Für Argumente, deren Typ eine class template -Spezialisierung ist, werden zusätzlich zu den Klassenregeln die folgenden zugehörigen Klassen und Namensräume zur Menge hinzugefügt.
a) Die Typen aller für Typ-Template-Parameter bereitgestellten Template-Argumente (Überspringen von konstanten Template-Parametern und Überspringen von Template-Template-Parametern).
b) Die Namespaces, in denen Template-Template-Argumente Mitglieder sind.
c) Die Klassen, in denen Template-Template-Argumente Mitglieder sind (falls es sich um Klassenmitgliedstemplates handelt).
4) Für Argumente vom Aufzählungstyp wird der innerste umschließende Namespace der Deklaration des Aufzählungstyps zum Satz hinzugefügt. Wenn der Aufzählungstyp ein Mitglied einer Klasse ist, wird diese Klasse zum Satz hinzugefügt.
5) Für Argumente vom Typ Zeiger auf T oder Zeiger auf ein Array von T wird der Typ T untersucht und sein zugehöriger Satz von Klassen und Namensräumen zum Satz hinzugefügt.
6) Für Argumente vom Funktionstyp werden die Funktionsparametertypen und der Funktionsrückgabetyp untersucht und ihre zugehörigen Mengen von Klassen und Namensräumen zur Menge hinzugefügt.
7) Für Argumente vom Typ Zeiger auf Memberfunktion F der Klasse X werden die Funktionsparametertypen, der Funktionstyp und die Klasse X untersucht und ihre zugehörigen Mengen von Klassen und Namensräumen zur Menge hinzugefügt.
8) Für Argumente vom Typ Zeiger auf Datenelement T der Klasse X werden sowohl der Member-Typ als auch der Typ X untersucht und ihre zugehörigen Mengen von Klassen und Namensräumen zur Menge hinzugefügt.
9) Wenn das Argument der Name oder der Adressoperator-Ausdruck für eine Menge überladener Funktionen (oder Funktions-Templates) ist, wird jede Funktion in der Überladungsmenge untersucht und ihr zugehöriger Satz von Klassen und Namensräumen wird zum Satz hinzugefügt.
  • Zusätzlich, wenn die Überladungsmenge durch einen Template-Bezeichner benannt wird, werden alle seine Typ-Template-Argumente und Template-Template-Argumente (aber keine konstanten Template-Argumente) untersucht und ihre zugehörigen Sätze von Klassen und Namensräumen werden zum Satz hinzugefügt.

Wenn ein beliebiger 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 beliebiger 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 der zugehörige Satz von Klassen und Namensräumen bestimmt wurde, werden alle Deklarationen, die in Klassen dieses Satzes gefunden werden, für den weiteren ADL-Prozess verworfen, mit Ausnahme von namespace-spezifischen Friend-Funktionen und Funktionsvorlagen, wie in Punkt 2 unten angegeben.

Die Menge der Deklarationen, die durch gewöhnliche unqualified lookup gefunden werden, und die Menge der Deklarationen, die in allen Elementen des durch ADL erzeugten assoziierten Sets gefunden werden, werden mit folgenden speziellen Regeln zusammengeführt:

1) using directives in den zugehörigen Namensräumen werden ignoriert.
2) Namensbereichsweite Friend-Funktionen (und Funktions-Templates), die in einer assoziierten Klasse deklariert werden, sind über ADL sichtbar, selbst wenn sie über gewöhnliche Suche nicht sichtbar sind.
3) Alle Namen mit Ausnahme der Funktionen und Funktionsvorlagen werden ignoriert (keine Kollision mit Variablen).

Hinweise

Aufgrund des argumentabhängigen Namenslookups werden nicht-Member-Funktionen und nicht-Member-Operatoren, die im selben Namespace wie eine Klasse definiert sind, als Teil der öffentlichen Schnittstelle dieser Klasse betrachtet (wenn sie durch ADL gefunden werden) [2] .

ADL ist der Grund für die etablierte Idiom zum Austauschen zweier Objekte in generischem Code: using std:: swap ; swap ( obj1, obj2 ) ; weil 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 der unqualifizierte Aufruf swap ( obj1, obj2 ) ohne benutzerdefinierte Überladung nichts aufrufen würde. Insbesondere verwenden std::iter_swap und alle anderen Standardbibliothek-Algorithmen diesen Ansatz beim Umgang mit Swappable Typen.

Die Namenssucheregeln 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 Vectors/Paars sind benutzerdefinierte Typen, die ihren eigenen Namespace zur ADL hinzufügen würden). Solche Operatoren würden nicht von Template-Instanziierungen aus gesucht, wie z.B. den Standardbibliothek-Algorithmen. Siehe abhängige Namen für weitere Details.

ADL kann eine friend-Funktion (typischerweise einen überladenen Operator) finden, die vollständig innerhalb einer Klasse oder Klassenvorlage definiert ist, selbst wenn sie niemals auf Namespace-Ebene deklariert wurde.

template<typename T>
struct number
{
    number(int);
    friend number gcd(number x, number y) { return 0; }; // Definition innerhalb
                                                         // einer Klassenvorlage
};
// Sofern keine passende Deklaration bereitgestellt wird, ist gcd
// ein unsichtbares (außer durch ADL) Mitglied dieses Namensraums
void g()
{
    number<double> a(3), b(4);
    a = gcd(a, b); // Findet gcd, weil number<double> eine assoziierte Klasse ist,
                   // wodurch gcd in seinem Namensraum (globaler Gültigkeitsbereich) sichtbar wird
//  b = gcd(3, 4); // Fehler; gcd ist nicht sichtbar
}

Obwohl ein Funktionsaufruf durch ADL aufgelöst werden kann, selbst wenn die gewöhnliche Suche nichts findet, erfordert ein Funktionsaufruf an eine Funktionsvorlage mit explizit angegebenen Template-Argumenten, dass eine Deklaration der Vorlage durch gewöhnliche Suche gefunden wird (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 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
}
(bis C++20)

In den folgenden Kontexten findet ausschließlich ADL-Lookup statt (d.h. Suche nur in assoziierten Namensräumen):

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

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); // Ruft B::f auf (Endlosschleife)
    }
    void g(A::X x)
    {
        g(x); // Fehler: Mehrdeutig zwischen B::g (gewöhnliche Suche)
              //        und A::g (argumentabhängige Suche)
    }
    void h(A::Y y)
    {
        h(y); // Ruft B::h auf (Endlosschleife): ADL untersucht den A-Namensraum
              // findet aber kein A::h, daher wird nur B::h aus gewöhnlicher Suche verwendet
    }
}

Fehlerberichte

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

DR Angewendet auf Verhalten wie veröffentlicht Korrektes Verhalten
CWG 33 C++98 Die zugeordneten Namensräume oder Klassen sind nicht spezifiziert
wenn ein für die Suche verwendetes Argument die Adresse einer
Gruppe überladener Funktionen oder einer Funktionsvorlage ist
spezifiziert
CWG 90 C++98 Die zugeordneten Klassen einer geschachtelten Nicht-Union-Klasse
enthielten nicht ihre einschließende Klasse, aber eine geschachtelte
Union war ihrer einschließenden Klasse zugeordnet
Auch Nicht-Unions zugeordnet
CWG 239 C++98 Eine Blockbereich-Funktionsdeklaration, die in der gewöhnlichen
unqualifizierten Suche gefunden wurde, verhinderte nicht das Auftreten von ADL
ADL nicht berücksichtigt außer
für using Deklarationen
CWG 997 C++98 Abhängige Parametertypen und Rückgabetypen waren
von der Berücksichtigung bei der Bestimmung der zugeordneten
Klassen und Namensräume einer Funktionsvorlage ausgeschlossen
eingeschlossen
CWG 1690 C++98
C++11
ADL konnte Lambdas (C++11) oder Objekte
von lokalen Klassentypen (C++98), die zurückgegeben werden, nicht finden
Sie können gefunden werden
CWG 1691 C++11 ADL zeigte überraschende Verhaltensweisen für opaque Enum-Deklarationen behoben
CWG 1692 C++98 Doppelt geschachtelte Klassen hatten keine zugeordneten Namensräume
(ihre einschließenden Klassen sind keine Mitglieder eines Namensraums)
Zugeordnete Namensräume sind
auf die innersten
einschließenden Namensräume erweitert
CWG 2857 C++98 Die zugeordneten Klassen eines unvollständigen
Klassentyps schlossen seine Basisklassen ein
Nicht eingeschlossen

Siehe auch

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)