Vorlagenargumentabzug für Zeigerparameter variadic function - Behandlung von mehrdeutigen Fällen

8

Betrachten Sie den folgenden Code:

%Vor%

Die Absicht ist, jede der Zeilen im Körper von main() separat zu versuchen. Meine Erwartungen waren, dass alle vier Aufrufe mehrdeutig waren und zu Compiler-Fehlern führen würden.

Ich habe den Code getestet:

  • Clang 3.6.0 und GCC 4.9.2, beide mit -Wall -Wextra -pedantic -std=c++14 ( -std=c++1y für GCC) - in allen diesen Fällen das gleiche Verhalten, abgesehen von geringfügigen Unterschieden im Wortlaut der Fehlermeldungen;
  • Visual C ++ 2013 Update 4 und Visual C ++ 2015 CTP6 - wieder dasselbe Verhalten, daher werde ich sie in Zukunft "MSVC" nennen.

Clang und GCC:

  • #1 : Compiler Fehler, mit einer verwirrenden Nachricht, im Grunde no overload of 'f' matching 'void (*)()' . Was? Woher kam die No-Param-Deklaration?
  • #3 : Compilerfehler, mit einer weiteren verwirrenden Nachricht: couldn't infer template argument 'T' . Von all den Dingen, die dort scheitern könnten, wäre das Argument für T das letzte, das ich erwarten würde ...
  • #2 und #4 : Kompiliert ohne Fehler und ohne Warnungen und wählt die erste Überladung.

Wenn in allen vier Fällen eine der Überladungen (eine beliebige) eliminiert wird, wird der Code kompiliert und die verbleibende Funktion ausgewählt. Das sieht in Clang und GCC nach einer Inkonsistenz aus: Denn wenn der Abzug für beide Überladungen separat gelingt, wie kann dann einer in den Fällen #2 und #4 gewählt werden? Sind das nicht beide perfekte Übereinstimmungen?

Nun, MSVC:

  • #1 , #3 und #4 : Compiler-Fehler, mit einer netten Nachricht: cannot deduce template argument as function argument is ambiguous . Jetzt spreche ich darüber! Aber warte ...

  • #2 : Kompiliert ohne Fehler und ohne Warnungen und wählt die erste Überladung aus. Probieren Sie die zwei Überladungen getrennt aus, nur die erste stimmt überein. Die zweite erzeugt einen Fehler: cannot convert argument 1 from 'void (*)(int,short)' to 'void (*)(int)' . Nicht mehr so ​​gut.

Um zu verdeutlichen, was ich mit case #2 suche, ist dies der Standard (N4296, erster Entwurf nach C ++ 14 final) in [14.8.1p9]:

  

Die Vorlagenargumentableitung kann die Sequenz der Vorlage erweitern   Argumente, die einem Template-Parameterpaket entsprechen, auch wenn   Sequenz enthält explizit angegebene Vorlagenargumente.

Sieht so aus, als ob dieser Teil in MSVC nicht ganz funktioniert, indem er die erste Überladung für #2 wählt.

Bisher sieht MSVC zwar nicht ganz richtig aus, ist aber zumindest relativ konsistent. Was ist mit Clang und GCC los? Was ist das richtige Verhalten gemäß dem Standard für jeden Fall?

    
bogdan 02.04.2015, 16:09
quelle

1 Antwort

7

Soweit ich das beurteilen kann, sind Clang und GCC in allen vier Fällen gemäß dem Standard richtig, auch wenn ihr Verhalten nicht intuitiv erscheint, besonders in den Fällen #2 und #4 .

Bei der Analyse der Funktionsaufrufe im Codebeispiel gibt es zwei Hauptschritte. Die erste ist die Ableitung und Ersetzung von Vorlagenargumenten. Wenn dies abgeschlossen ist, gibt es eine Deklaration einer Spezialisierung (von g oder h ), in der alle Vorlagenparameter durch tatsächliche Typen ersetzt wurden.

Der zweite Schritt versucht dann, die Überladungen von f mit dem tatsächlichen Parameter pointer-to-function zu vergleichen, der im vorherigen Schritt erstellt wurde. Die beste Übereinstimmung wird gemäß den Regeln in [13.4] gewählt - Adresse der überladenen Funktion; In unseren Fällen ist das ziemlich einfach, da es keine Vorlagen unter den Überladungen gibt, also haben wir entweder eine perfekte Übereinstimmung oder gar keine.

Der Schlüssel zum Verständnis, was hier passiert, ist, dass eine Mehrdeutigkeit im ersten Schritt nicht notwendigerweise bedeutet, dass der gesamte Prozess fehlschlägt.

Die folgenden Zitate stammen von N4296, aber der Inhalt hat sich seit C ++ 11 nicht geändert.

[14.8.2.1p6] beschreibt den Prozess der Vorlageargumentableitung, wenn ein Funktionsparameter ein Zeiger auf Funktion ist (Hervorhebung meins):

  

Wenn P ein Funktionstyp ist, weisen Sie auf den Funktionstyp oder Zeiger auf   Elementfunktionstyp:
  - Wenn das Argument eine Überladungsmenge ist, die eine oder mehrere Funktionen enthält   Vorlagen, wird der Parameter als nicht abgeleiteter Kontext behandelt.
  - Wenn das Argument eine Überladungsmenge ist (nicht   Funktions-Templates enthalten) versucht, den Argumentabzug zu testen   Verwenden jedes der Mitglieder des Satzes. Wenn der Abzug nur erfolgreich ist   eines der Mitglieder der Überladungsgruppe, dieses Mitglied wird als Argument verwendet   Wert für den Abzug. Wenn der Abzug mehr als einen Erfolg hat   Mitglied der Überladungsmenge Der Parameter wird als nicht abgeleitet behandelt   Kontext .

Der Vollständigkeit halber stellt [14.8.2.5p5] klar, dass die gleiche Regel auch dann gilt, wenn keine Übereinstimmung gefunden wird:

  

Die nicht abgeleiteten Kontexte sind: [...]
  - Ein Funktionsparameter, für den keine Argumentableitung durchgeführt werden kann, weil   Das zugehörige Funktionsargument ist eine Funktion oder eine Menge überladen   Funktionen (13.4), und eine oder mehrere der folgenden gelten:
  - mehr als eine Funktion entspricht   der Funktionsparameter-Typ (was zu einer mehrdeutigen Ableitung führt) oder
  - Keine Funktion entspricht dem Funktionsparametertyp oder dem Typ   - Die Menge der Funktionen, die als Argument zur Verfügung gestellt werden, enthält einen oder mehrere   Funktionsvorlagen.

Also, keine schweren Fehler wegen Mehrdeutigkeit in diesen Fällen. Stattdessen sind alle Vorlagenparameter in allen unseren Fällen in nicht-abgeleiteten Zusammenhängen. Dies kombiniert mit [14.8.1p3]:

  

[...] Ein nachfolgendes Template-Parameterpaket (14.5.3) nicht anders   deduced wird auf eine leere Sequenz von Template-Argumenten zurückgeführt.   [...]

Während die Verwendung des Wortes "deduced" hier verwirrend ist, nehme ich an, dass ein Template-Parameterpack auf die leere Sequenz gesetzt wird, wenn keine Elemente aus einer beliebigen Quelle abgeleitet werden können und es keine Template-Argumente explizit gibt dafür angegeben.

Jetzt sind die Fehlermeldungen von Clang und GCC sinnvoll (eine Fehlermeldung, die nur Sinn macht nachdem Sie verstehen, warum der Fehler auftritt, ist nicht genau die Definition einer hilfreichen Fehlermeldung, sondern Ich denke, es ist besser als nichts):

  • #1 : Da Ts die leere Sequenz ist, ist der Parameter von g 's Spezialisierung in diesem Fall tatsächlich void (*)() . Der Compiler versucht dann, eine der Überladungen mit dem Zieltyp abzugleichen und schlägt fehl.
  • #3 : T erscheint nur in einem nicht-abgeleiteten Kontext und ist nicht explizit angegeben (und es ist kein Parameterpaket, daher kann es nicht "leer" sein), daher kann für h keine Spezialisierungsdeklaration erstellt werden die Nachricht.

Für die Fälle, die kompilieren:

  • #2 : Ts kann nicht abgeleitet werden, aber ein Template-Parameter ist explizit dafür angegeben, also Ts ist int , was g 's Spezialisierungsparameter void (*)(int) ergibt. Die Überladungen werden dann mit diesem Zieltyp abgeglichen, und der erste wird ausgewählt.
  • #4 : T wird explizit als int angegeben und Ts ist die leere Sequenz, also ist h 's Spezialisierung Parameter void (*)(int) , das gleiche wie oben.

Wenn wir eine der Überladungen eliminieren, eliminieren wir die Mehrdeutigkeit während der Schablonenargumentableitung, so dass die Schablonenparameter nicht länger in nicht-abgeleiteten Kontexten sind, wodurch sie entsprechend der verbleibenden Überladung abgeleitet werden können.

Eine schnelle Überprüfung ist das Hinzufügen einer dritten Überladung

%Vor% Mit

kann die Groß- / Kleinschreibung #1 kompiliert werden, was mit all dem übereinstimmt.

Ich nehme an, dass die Dinge auf diese Weise spezifiziert wurden, damit Template-Argumente, die in Pointer-to-Function-Parametern enthalten sind, aus anderen Quellen wie anderen Funktionsargumenten oder explizit angegebenen Template-Argumenten erhalten werden können, selbst wenn die Template-Argumentableitung nicht möglich ist basierend auf dem Zeiger-zu-Funktion-Parameter selbst. Dadurch kann in vielen Fällen eine Deklaration der Funktionsschablonenspezialisierung erstellt werden. Da die Überladungen dann mit dem Parameter der synthetisierten Spezialisierung verglichen werden, bedeutet dies, dass wir eine Möglichkeit haben, eine Überladung auszuwählen, selbst wenn die Vorlagenargumentableitung nicht eindeutig ist. Ziemlich nützlich, wenn Sie danach suchen, in anderen Fällen schrecklich verwirrend - wirklich nichts Ungewöhnliches.

Das lustige ist, dass MSVCs Fehlermeldung zwar scheinbar nett und hilfreich ist, aber für #1 irreführend ist, etwas, aber nicht sehr hilfreich für #3 und falsch für #4 . Auch sein Verhalten für #2 ist ein Nebeneffekt eines separaten Problems bei seiner Implementierung, wie in der Frage erläutert; Wäre dies nicht der Fall, würde wahrscheinlich auch für #2 dieselbe falsche Fehlermeldung ausgegeben.

Das soll nicht heißen, dass ich die Fehlermeldungen von Clang und GCC für #1 und #3 mag; Ich denke, sie sollten zumindest eine Notiz über den nicht-abgeleiteten Kontext und den Grund dafür enthalten.

    
bogdan 02.04.2015 16:09
quelle