Warum wird bei einer zweiphasigen Suche die überladene Version von 'swap' nicht ausgewählt?

8

Ich studiere diese faszinierende Antwort zu subtile Frage in Bezug auf die bewährte Methode zum Implementieren der Funktion swap für benutzerdefinierte Typen . (Meine Frage wurde ursprünglich durch eine Diskussion über die Illegalität des Hinzufügens von Typen zum Namespace std motiviert.)

Ich werde das Code-Snippet nicht von der oben verlinkten Antwort hier ausdrucken.

Stattdessen möchte ich die Antwort verstehen .

Die Antwort, die ich oben verlinkt habe, gibt unter dem ersten Codeausschnitt an, überladen swap in namespace std (anstatt darauf zu spezialisieren ) Namensraum):

  

Wenn Ihr Compiler etwas anderes ausgibt, ist es das nicht   "Zwei-Phasen-Lookup" für Vorlagen richtig implementieren.

Die Antwort geht dann darauf hinaus, dass specializing swap in namespace std (im Gegensatz zu overloading es) ein anderes Ergebnis erzeugt (das gewünschte Ergebnis im Falle von Spezialisierung ).

Die Antwort wird jedoch mit einem zusätzlichen Fall fortgesetzt: spezialisierter Swap für eine benutzerdefinierte Vorlage Klasse - in diesem Fall wird wiederum das gewünschte Ergebnis nicht erreicht.

Leider sagt die Antwort einfach die Fakten ; es erklärt nicht warum .

Kann jemand diese Antwort näher erläutern und den Suchvorgang in den zwei spezifischen Code-Snippets beschreiben, die in dieser Antwort enthalten sind:

  • Überladen von swap in namespace std für eine benutzerdefinierte Nicht-Template-Klasse (wie im ersten Code-Snippet der verknüpften Antwort)

  • spezialisiert auf swap in namespace std für eine benutzerdefinierte Vorlagenklasse (wie im endgültigen Codeausschnitt der verknüpften Antwort)

In beiden Fällen wird das generische std::swap und nicht das benutzerdefinierte swap aufgerufen. Warum?

(Dies wird Aufschluss geben über die Art der Zweiphasen-Suche und den Grund für die Best Practice zur Implementierung von benutzerdefinierten swap ; danke.)

    
Dan Nissenbaum 27.01.2014, 15:20
quelle

2 Antworten

7

Präambel mit vielen Standarden

Der Aufruf von swap() im Beispiel führt zu einem abhängigen Namen, da seine Argumente begin[0] und begin[1] vom Vorlagenparameter T der umgebenden algorithm() -Funktionsvorlage abhängen. Die Suche nach zweiphasigen Namen für solche abhängigen Namen ist im Standard wie folgt definiert:

14.6.4.2 Kandidatenfunktionen [temp.dep.candidate]

  

1 Für einen Funktionsaufruf, bei dem der Postfix-Ausdruck ein abhängiger Name ist,   Die Kandidatenfunktionen werden mit den üblichen Suchregeln gefunden (3.4.1,   3.4.2) außer dass:

     

- Für den Teil der Suche, der die Verwendung von unqualifiziertem Namen (3.4.1) verwendet, werden nur Funktionsdeklarationen aus der Templatendefinition verwendet   Kontext gefunden werden.

     

- Für den Teil des Lookup, der assoziierte verwendet   Namespaces (3.4.2), nur Funktionsdeklarationen, die entweder in der   Template De fi nition Context oder der Template Instanziierungskontext sind   gefunden.

Unqualifizierte Suche ist definiert durch

3.4.1 Unqualifizierte Namenssuche [basic.lookup.unqual]

  

1 In allen Fällen, die in 3.4.1 aufgeführt sind, werden die Bereiche nach a durchsucht   Erklärung in der Reihenfolge, die in jeder der jeweiligen Kategorien aufgeführt ist;    Name-Suche endet, sobald eine Deklaration gefunden wird für den Namen. Wenn nein   Erklärung gefunden, das Programm ist schlecht gebildet.

und argumentabhängige Suche (ADL) als

3.4.2 Argumentabhängige Namenssuche [basic.lookup.argdep]

  

1 Wenn der postfix-Ausdruck in einem Funktionsaufruf (5.2.2) ist   unqualifizierte ID , andere Namespaces werden während des üblichen nicht berücksichtigt   unqualifizierte Suche (3.4.1) kann gesucht werden, und in diesen Namespaces,   Namespace-Bereich Friend-Funktion oder Funktion Vorlage Deklarationen   (11.3) kann nicht anders sichtbar gefunden werden. Diese Änderungen an der   Suche hängt von den Typen der Argumente ab (und für die Vorlagenvorlage)   Argumente, der Namespace des Template-Arguments).

Anwenden des Standards auf das Beispiel

Das erste Beispiel ruft exp::swap() auf. Dies ist kein abhängiger Name und erfordert keine zweiphasige Namenssuche. Da der Aufruf zum Auslagern qualifiziert ist, findet eine gewöhnliche Suche statt, die nur die generische swap(T&, T&) -Funktionsvorlage findet.

Das zweite Beispiel (was @HowardHinnant "die moderne Lösung" nennt) ruft swap() auf und hat auch eine Überladung swap(A&, A&) im selben Namespace wie wo class A lebt (der globale Namespace) in diesem Fall). Da der Aufruf zum Swap unqualifiziert ist, finden gewöhnliches Nachschlagen und ADL zum Zeitpunkt der Definition statt (wobei wiederum nur das generische swap(T&, T&) gefunden wird), aber ein anderes ADL findet am Instanziierungspunkt statt (dh wo exp::algorithm() aufgerufen wird main() ) und dies nimmt swap(A&, A&) auf, was eine bessere Übereinstimmung bei der Überladungsauflösung ist.

So weit, so gut. Nun zur Zugabe: Das dritte Beispiel ruft swap() auf und hat eine Spezialisierung template<> swap(A&, A&) in namespace exp . Die Suche ist die gleiche wie im zweiten Beispiel, aber ADL greift die Template-Spezialisierung nicht auf, da sie nicht in einem zugeordneten Namespace von class A enthalten ist. Obwohl die Spezialisierung template<> swap(A&, A&) bei der Überladungsauflösung keine Rolle spielt, wird sie dennoch am Verwendungsort instanziiert.

Schließlich ruft das vierte Beispiel swap() auf und hat eine Überladung template<class T> swap(A<T>&, A<T>&) innerhalb namespace exp für template<class T> class A lebt im globalen Namespace. Die Suche ist die gleiche wie im dritten Beispiel, und ADL greift die Überladung swap(A<T>&, A<T>&) nicht erneut auf, da sie nicht in einem zugeordneten Namespace der Klassenvorlage A<T> enthalten ist. Und in diesem Fall gibt es auch keine Spezialisierung, die am Verwendungsort instanziiert werden muss, so dass hier das generische swap(T&, T&) aufgerufen wird.

Fazit

Obwohl Sie keine neuen Überladungen zu namespace std hinzufügen dürfen und nur explizite Spezialisierungen, würde es aufgrund der verschiedenen Feinheiten der zweiphasigen Namenssuche nicht einmal funktionieren.

    
TemplateRex 27.01.2014, 15:41
quelle
4

Es ist unmöglich, swap in namespace std für einen benutzerdefinierten Typ zu überladen. Einleitung eine Überlastung (im Gegensatz zu einer Spezialisierung) in namespace std ist undefiniertes Verhalten (illegal unter dem Standard, keine Diagnose erforderlich).

Es ist unmöglich, eine Funktion im Allgemeinen für eine Klasse template zu spezialisieren (im Gegensatz zu einer template -Klasse Instanz - dh std::vector<int> ist eine Instanz, während std::vector<T> ist die gesamte Klasse template ). Was als Spezialisierung erscheint, ist eigentlich eine Überlastung. Also gilt der erste Absatz.

Die beste Methode zum Implementieren von benutzerdefinierten swap ist das Einführen einer swap Funktion oder Überladung in denselben Namespace, in dem Ihre template oder class lebt.

Wenn swap dann im richtigen Kontext ( using std::swap; swap(a,b); ) aufgerufen wird, wie es in std library aufgerufen wird, wird ADL aktiviert, und Ihre Überladung wird gefunden.

Die andere Option ist eine vollständige Spezialisierung von swap in std für Ihren speziellen Typ. Dies ist unmöglich (oder unpraktisch) für template -Klassen, da Sie sich für jede Instanz Ihrer vorhandenen Klasse template spezialisieren müssen. Für andere Klassen ist es fragil, da Spezialisierung nur für diesen bestimmten Typ gilt: Unterklassen müssen ebenfalls in std angepasst werden.

Im Allgemeinen ist die Spezialisierung von Funktionen äußerst fragil, und Sie sollten besser Außerkraftsetzungen einführen. Da du keine Überschreibungen in std einführen kannst, ist der einzige Ort, an dem sie zuverlässig gefunden werden, dein eigenes namespace . Solche Überschreibungen in Ihrem eigenen Namespace werden gegenüber Overrides in std ebenfalls bevorzugt.

Es gibt zwei Möglichkeiten, ein swap in Ihren Namespace zu injizieren. Beide arbeiten für diesen Zweck:

%Vor%

Das erste ist es, es direkt in namespace zu injizieren, das zweite ist, es als inline friend einzuschließen. Die Inline friend für "externe Operatoren" ist ein allgemeines Muster, das im Grunde bedeutet, dass Sie swap nur über ADL finden können, aber in diesem speziellen Kontext nicht viel hinzufügen.

    
Yakk 27.01.2014 16:28
quelle