Der folgende Code lässt sich gut mit Clang-Trunk im C ++ 17-Modus kompilieren, bricht jedoch im C ++ 2a-Modus (bevorstehendes C ++ 20) ab:
// Meta struct describing the result of a comparison
struct Meta {};
struct Foo {
Meta operator==(const Foo&) {return Meta{};}
Meta operator!=(const Foo&) {return Meta{};}
};
int main()
{
Meta res = (Foo{} != Foo{});
}
Es lässt sich auch gut mit gcc-trunk oder clang-9.0.0 kompilieren: https://godbolt.org/z/8GGT78
Der Fehler mit Clang-Trunk und -std=c++2a
:
<source>:12:19: error: use of overloaded operator '!=' is ambiguous (with operand types 'Foo' and 'Foo')
Meta res = (f != g);
~ ^ ~
<source>:6:10: note: candidate function
Meta operator!=(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function
Meta operator==(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function (with reversed parameter order)
Ich verstehe, dass C ++ 20 es möglich macht, nur zu überladen, operator==
und der Compiler automatisch generiert, operator!=
indem er das Ergebnis von negiert operator==
. Soweit ich weiß, funktioniert dies nur, solange der Rückgabetyp ist bool
.
Die Ursache des Problems ist , dass in Eigen wir eine Reihe von Operatoren erklären ==
, !=
, <
, ... zwischen Array
Objekten oder Array
und Skalare, die Rückkehr (ein Ausdruck) ein Array von bool
(die dann elementweise zugegriffen werden kann, oder anderweitig verwendet werden ). Z.B,
#include <Eigen/Core>
int main()
{
Eigen::ArrayXd a(10);
a.setRandom();
return (a != 0.0).any();
}
Im Gegensatz zu meinem obigen Beispiel schlägt dies sogar mit gcc-trunk fehl: https://godbolt.org/z/RWktKs . Ich habe es noch nicht geschafft, dies auf ein Nicht-Eigen-Beispiel zu reduzieren, das sowohl im Clang-Trunk als auch im Gcc-Trunk fehlschlägt (das Beispiel oben ist ziemlich vereinfacht).
Zugehöriger Problembericht: https://gitlab.com/libeigen/eigen/issues/1833
Meine eigentliche Frage: Ist dies tatsächlich eine bahnbrechende Änderung in C ++ 20 (und besteht die Möglichkeit, die Vergleichsoperatoren zu überladen, um Meta-Objekte zurückzugeben), oder handelt es sich eher um eine Regression in clang / gcc?
Antworten:
Das Eigenproblem scheint sich auf Folgendes zu reduzieren:
Die beiden Kandidaten für den Ausdruck sind
operator==(const Scalar&, const Derived&)
Base<X>::operator!=(const Scalar&) const
Per [over.match.funcs] / 4 ist der Typ des impliziten Objektparameters für # 2 , da er
operator!=
nichtX
durch eine using-Deklaration in den Gültigkeitsbereich von importiert wurdeconst Base<X>&
. Infolgedessen hat # 1 eine bessere implizite Konvertierungssequenz für dieses Argument (exakte Übereinstimmung statt Konvertierung von abgeleitet zu Basis). Wenn Sie # 1 auswählen, wird das Programm schlecht geformt.Mögliche Korrekturen:
using Base::operator!=;
zuDerived
oderoperator==
, um aconst Base&
anstelle von a zu nehmenconst Derived&
.quelle
bool
von ihrem zurückgeben konnteoperator==
? Denn dies scheint der einzige Grund zu sein, warum der Code nach den neuen Regeln schlecht geformt ist.operator==(Array, Scalar)
elementweisen Vergleich und gibt einenArray
von zurückbool
. Sie können das nicht in ein verwandeln,bool
ohne alles andere zu zerbrechen.operator==
sollten sich nicht auf vorhandenen Code auswirken, tun dies jedoch in diesem Fall, da die Prüfung auf einenbool
Rückgabewert nicht Teil der Auswahl von Kandidaten für das Umschreiben ist.Ja, der Code bricht tatsächlich in C ++ 20 ab.
Der Ausdruck
Foo{} != Foo{}
hat drei Kandidaten in C ++ 20 (während es in C ++ 17 nur einen gab):Dies ergibt sich aus den neuen umgeschriebenen Kandidatenregeln in [over.match.oper] /3.4 . Alle diese Kandidaten sind lebensfähig, da unsere
Foo
Argumente dies nicht sindconst
. Um den besten Kandidaten zu finden, müssen wir unsere Tiebreaker durchgehen.Die relevanten Regeln für die bestmögliche Funktion lauten ab [over.match.best] / 2 :
#2
und#3
sind umgeschriebene Kandidaten und#3
haben die Reihenfolge der Parameter umgekehrt, während sie#1
nicht umgeschrieben werden. Aber um zu diesem Tiebreaker zu gelangen, müssen wir zuerst diese Anfangsbedingung durchstehen: Für alle Argumente sind die Konvertierungssequenzen nicht schlechter.#1
ist besser als#2
weil alle Konvertierungssequenzen gleich sind (trivial, weil die Funktionsparameter gleich sind) und#2
ein umgeschriebener Kandidat ist, während dies#1
nicht der Fall ist.Aber ... beide Paare
#1
/#3
und#2
/#3
bleiben bei dieser ersten Bedingung hängen. In beiden Fällen hat der erste Parameter eine bessere Konvertierungssequenz für#1
/,#2
während der zweite Parameter eine bessere Konvertierungssequenz für hat#3
(der Parameter,const
der einer zusätzlichenconst
Qualifizierung unterzogen werden muss, hat also eine schlechtere Konvertierungssequenz). Diesesconst
Flip-Flop führt dazu, dass wir keines von beiden bevorzugen können.Infolgedessen ist die gesamte Überlastungsauflösung nicht eindeutig.
Das stimmt nicht Wir betrachten bedingungslos umgeschriebene und rückgängig gemachte Kandidaten. Die Regel, die wir haben, lautet aus [over.match.oper] / 9 :
Das heißt, wir betrachten diese Kandidaten immer noch. Wenn jedoch der am besten geeignete Kandidat ein Kandidat ist
operator==
, der beispielsweise zurückkehrt, istMeta
das Ergebnis im Grunde das gleiche, als ob dieser Kandidat gelöscht worden wäre.Wir wollten nicht in einem Zustand sein, in dem die Überlastungsauflösung den Rückgabetyp berücksichtigen müsste. Und auf jeden Fall ist die Tatsache, dass der Code hier zurückgegeben
Meta
wird, unerheblich - das Problem würde auch bestehen, wenn er zurückgegeben wirdbool
.Zum Glück ist die Lösung hier einfach:
Sobald Sie beide Vergleichsoperatoren erstellt haben
const
, gibt es keine Mehrdeutigkeit mehr. Alle Parameter sind gleich, daher sind alle Konvertierungssequenzen trivial gleich.#1
würde jetzt#3
durch nicht umgeschriebenes#2
schlagen und würde jetzt#3
durch nicht rückgängig gemacht schlagen - was#1
den besten lebensfähigen Kandidaten macht. Das gleiche Ergebnis wie in C ++ 17, nur noch ein paar Schritte, um dorthin zu gelangen.quelle
==
und der Rückgabetyp der ausgewählten Funktion nichtbool
. Dieses Keulen tritt jedoch nicht während der Überlastungsauflösung selbst auf.cv bool
(und vor dieser Änderung war die Anforderung eine kontextbezogene Konvertierung zubool
- immer noch nicht!
)[over.match.best] / 2 listet auf, wie gültige Überladungen in einem Satz priorisiert werden. Abschnitt 2.8 sagt uns , dass
F1
besser als ist ,F2
wenn (unter vielen anderen Dingen):Das Beispiel dort zeigt ein explizites
operator<
Aufrufen, obwohloperator<=>
es vorhanden ist.Und [over.match.oper] /3.4.3 sagt uns, dass die Kandidatur von
operator==
unter diesen Umständen ein umgeschriebener Kandidat ist.Allerdings , Ihre Betreiber vergessen eine entscheidende Sache: sie sollten
const
Funktionen. Und wenn sie nicht verwendet werdenconst
, kommen frühere Aspekte der Überlastungsauflösung ins Spiel. Keine der beiden Funktionenconst
stimmt genau überein, daconst
für verschiedene Argumente Nicht- Konvertierungen durchgeführt werden müssen. Das verursacht die fragliche Mehrdeutigkeit.Sobald Sie sie erstellt haben
const
, wird Clang Trunk kompiliert .Ich kann nicht mit dem Rest von Eigen sprechen, da ich den Code nicht kenne, er sehr groß ist und daher nicht in eine MCVE passt.
quelle
const
haben die nicht umgekehrten Kandidaten eine bessere Konvertierungssequenz für das zweite Argument und der umgekehrte Kandidat eine bessere Konvertierungssequenz für das erste Argument.const
im Minimalbeispiel vergessen . Ich bin mir ziemlich sicher, dass Eigenconst
überall verwendet (oder außerhalb von Klassendefinitionen, auch mitconst
Referenzen), aber ich muss das überprüfen. Ich versuche, den Gesamtmechanismus, den Eigen verwendet, auf ein minimales Beispiel zu reduzieren, wenn ich die Zeit finde.Wir haben ähnliche Probleme mit unseren Goopax-Header-Dateien. Das Kompilieren des Folgenden mit clang-10 und -std = c ++ 2a erzeugt einen Compilerfehler.
Die Bereitstellung dieser zusätzlichen Operatoren scheint das Problem zu lösen:
quelle
a == 0
zusammengestellt haben ?gpu_bool gpu_type<T>::operator==(T a) const;
undgpu_bool gpu_type<T>::operator!=(T a) const;
mit C ++ - 17 funktioniert dies einwandfrei. Aber jetzt mit clang-10 und C ++ - 20 werden diese nicht mehr gefunden, und stattdessen versucht der Compiler, seine eigenen Operatoren durch Austauschen der Argumente zu generieren, und dies schlägt fehl, da der Rückgabetyp dies nicht istbool
.