2016-08-25 1 views
3

Ich habe Probleme zu verstehen, die Auswirkungen der Konvertierungsfunktion Vorlage Argument Deduktionsregeln in der C++ - Standard. Der Standard gibt an, dass ([temp.deduct.conv] Klausel 1, in §14.8.2.3.1 N4594):Auswirkungen der Konvertierung Funktionsvorlage Argumentabzug in C++

Template argument deduction is done by comparing the return type of the conversion function template (call it P) with the type that is required as the result of the conversion (call it A; see 8.5, 13.3.1.5, and 13.3.1.6 for the determination of that type) as described in 14.8.2.5.

wo 14.8.2.5 ([temp.deduct.type]) der Abschnitt ist, der beschreibt, allgemeine Vorlage Argumentabzug (obwohl der häufigste Fall, Funktion Anruf Vorlage Argument Abzug [temp.deduct.call], scheint nicht mehr darauf hindeuten, hat es jemals?). Der nächste Satz ist das, was mich verwirrt, obwohl (Ziffer 2):

If P is a reference type, the type referred to by P is used in place of P for type deduction and for any further references to or transformations of P in the remainder of this section.

Für mich scheint zu implizieren, dass template <class T> operator T() und template <class T> operator T&() gleich sind (und beide Angabe in einer Mehrdeutigkeit führen würde). Aber das ist in keinem Compiler der Fall! Zum Beispiel:

struct any1 { template <typename T> operator T() { } }; 

struct any2 { template <typename T> operator T&() { } }; 

void f1(int) { } 
void f2(int&) { } 
void f3(int const&) { } 

int main() { 
    f1(any1()); 
    // f2(any1()); compile time error 
    f3(any1()); 

    f1(any2()); 
    f2(any2()); 
    f3(any2()); 
} 

Live Demo

Aber wenn Referenzen ignoriert werden, any1 und any2 sollten das gleiche Verhalten haben, nicht wahr? Natürlich nicht, da f2(any1()) nicht mit gcc oder clang kompiliert, während f2(any2()) kompiliert mit beiden.

Die nächste Klausel (Klausel 3, insbesondere 3.3) verwechselt Dinge noch weiter:

If A is not a reference type: [...] If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction.

Dies, zusammen mit Satz 2 über die Entfernung von Referenzen, würde zu implizieren scheint, dass der folgende Code soll da nicht kompiliert eine Mehrdeutigkeit:

struct any3 { 
    template <typename T> operator T&() { } 
    template <typename T> operator T const&() { } 
}; 

void f1(int) { } 

int main() { 
    f1(any3()); 
} 

Live Demo

und doch funktioniert dies sowohl mit gcc und Klappern in Ordnung.

Was fehlt mir?

bearbeiten

ich, dass die Art und Weise damit umgehen ist genau das Klirren und gcc-Compiler klären sollte, was ich von einem allgemeinen (relativ weit fortgeschritten) Verständnis von C++ erwarten. Einige Kommentatoren haben um Klärung gebeten, was meine Verwirrung ist (und implizit, warum ich mich darum kümmern sollte). Meine Verwirrung hier bezieht sich ausschließlich auf den Versuch, die Implikationen des Standards zu verstehen. Ich brauche ein klares Verständnis davon, weil ich ein Papier mit Code einreiche, der stark von dieser Arbeit abhängt und davon, dass ich normkonform verwende.

+1

Bitte geben Sie an, was in Ihrem ersten Code unerwartet ist und was erwartet wird. Haben Sie erwartet, dass die kommentierte Zeile kompiliert wird? Was bedeuten die anderen Linien? Die T-Zeile der kommentierten Zeile ist "int", soweit ich das sehen kann. Ein 'int' prvalue kann nicht verwendet werden, um ein 'int &' zu initialisieren. Während die Abzüge für die Fälle "T &" und "T" dieselben sein können, wird die Umwandlungsfunktion in einem Fall eine Referenz zurückgeben, und in der anderen nicht. Dies scheint mir klar zu sein. –

+0

@ JohannesSchaub-litb geklärt, danke.Der Punkt ist, dass, wenn Verweise in "P" ignoriert werden, wie der Standard zu implizieren scheint, "any1" und "any2" dasselbe Verhalten haben sollten. Der erste Code funktioniert so, wie ich es vom Raten erwarten würde, bevor ich den Standard lese (dh ein Prvalue kann nicht an eine Lvalue - Referenz, sondern an eine Lvalue - Referenz binden), aber er stimmt nicht mit dem überein, was ich beim Lesen der Standard. Das ist der Punkt der Verwirrung - die Interpretation des Standarddokuments selbst –

+0

Für den zweiten Fall denke ich, dass die zweite Vorlage spezialisierter ist und daher für die benutzerdefinierte Konvertierungssequenz ausgewählt wird (dies ist einer der Tie-Breaker, da ansonsten es wäre eine Zweideutigkeit). –

Antwort

3

Der Schlüsselpunkt, den Sie verpassen, ist, dass die Überladungsauflösung immer noch passieren muss. Vorlagenabzug ist nicht das Ende der Geschichte. Adressierung getrennt Ihre beiden Beispiele:


To me, this seems to imply that template <class T> operator T() and template <class T> operator T&() are the same (and specifying both would result in an ambiguity). But that isn't the case in any compiler I've used!

Der Text zeigt an, dass zitieren Abzug von T ist die gleiche für beide Konvertierungsoperatoren, das ist wahr. Aber die Betreiber selbst sind nicht gleich. Sie müssen zusätzlich die Regeln für die Bindung an Referenzen berücksichtigen, die in [dcl.init.ref] aufgelistet sind. Der Abschnitt ist zu lang, um kurz zu kopieren, aber der Grund, dass dies ein Fehler

f2(any1()); // error 

ist der gleiche Grund, dass f2(1) ist ein Fehler: Sie keine L-Wert Referenz const auf einen R-Wert auf nicht binden können. Als Ergebnis ist sogar beide Betreiber mit in sich selbst nicht zweideutig:

struct X { 
    template <class T> operator T(); // #1 
    template <class T> operator T&(); // #2 
}; 

f1(X{}); // error: ambiguous 
f2(X{}); // ok! #1 is not viable, calls #2 
f3(X{}); // ok! #2 is preferred (per [dcl.init.ref]/5.1.2) 

And yet this works fine with both gcc and clang.

struct any3 { 
    template <typename T> operator T&();  // #3 
    template <typename T> operator T const&() // #4 
}; 

void f1(int) { } 

int main() { 
    f1(any3()); 
} 

Dies ist ein interessantes Szenario so weit wie Compiler gehen, weil gcc hier einen Fehler hat. Beide Kandidaten sollten gültig sein (gcc berücksichtigt # 4 nicht aufgrund von 61663). Keiner der Tiebreaker trifft auf die Bestimmung des besten Kandidaten zu, so dass wir in diesem Fall auf [temp.deduct.partial] zurückgehen müssen, um zu bestimmen, welcher Kandidat spezieller ist ... was in diesem Fall # 4 ist.

2

Vorlage Argument Abzug für eine Funktionsvorlage ist nur ein Schritt in dem komplexen Prozess der Überladung Auflösung.

§13.3.1 Candidate functions and argument lists

...

7 In each case where a candidate is a function template, candidate function template specializations are generated using template argument deduction (14.8.3, 14.8.2).

Vorlage Argument Abzug für eine gegebene Funktion Matrize als ob keine andere Funktion Vorlage existiert. Lesen Sie den Abschnitt § 14.8.2.3 erneut, und Sie werden feststellen, dass Ihre Fragen zu einem anderen Teil des Standards gehören.

Nachdem die Template-Argumentableitung für alle Kandidaten-Template-Funktionen durchgeführt wurde, muss die beste funktionsfähige Funktion gemäß den Regeln von §13.3.3 ausgewählt werden.Wenn zu diesem Zeitpunkt zwei oder mehr Funktionsschablonenspezialisierungen in der Kandidatenfunktionsliste vorhanden sind, dann beinhaltet der Prozess zur Auswahl der besten funktionsfähigen Funktion partielle Ordnungsregeln, die in § 14.5.6.2 beschrieben sind (ich denke, dass dieser Abschnitt Antworten auf Ihre Fragen enthält).

2

Die Typableitung ist ein separater Schritt von der Überladungsauflösung und der semantischen Überprüfung.

struct any1 { template <typename T> operator T() { } }; 

struct any2 { template <typename T> operator T&() { } }; 

void f1(int) { } 
void f2(int&) { } 
void f3(int const&) { } 

int main() { 
    f1(any1()); 
    // f2(any1()); compile time error 
    f3(any1()); 

    f1(any2()); 
    f2(any2()); 
    f3(any2()); 
} 

Hier f2(any1()) und f2(any2()) verhalten sich identisch für Typ-Abzug. Beide folgern T=int. Aber dann wird T in die Original Deklaration ersetzt, um Mitglied Spezialisierungen any1::operator int() und any2::operator int&() zu erhalten. f2(any1().operator int()) ist ein semantischer Fehler, weil er versucht, einen nichtkonstanten Lvalue-Referenzfunktionsparameter an einen Rvalue-Ausdruck zu binden. Dies macht operator int() eine nicht lebensfähige Funktion; Wenn any1 andere Konvertierungsfunktionen hätten, könnten sie durch Überladungsauflösung ausgewählt werden.

struct any3 { 
    template <typename T> operator T&() { } 
    template <typename T> operator T const&() { } 
}; 

void f1(int) { } 

int main() { 
    f1(any3()); 
} 

Auch hier verhalten sich die beiden Template-Konvertierungsfunktionen identisch für die Typableitung. Beide folgern T=int. Dann wird dieser Abzug in die ursprünglichen Deklarationen eingesetzt, um operator int&() und operator int const&() zu erhalten. Dann vergleicht die Überladungsauflösung diese beiden. Durch das Lesen von Klausel 13 sind sie mehrdeutig, aber gcc chooses operator int&() and clang chooses operator int const&() ...

Verwandte Themen