Auflösen einer mehrdeutigen Überlastung des Funktionszeigers und der std :: -Funktion für ein Lambda mit +

93

Im folgenden Code ist der erste Aufruf von foomehrdeutig und kann daher nicht kompiliert werden.

Die zweite mit dem +vor dem Lambda hinzugefügten wird in die Überladung des Funktionszeigers aufgelöst.

#include <functional>

void foo(std::function<void()> f) { f(); }
void foo(void (*f)()) { f(); }

int main ()
{
    foo(  [](){} ); // ambiguous
    foo( +[](){} ); // not ambiguous (calls the function pointer overload)
}

Was macht die +Notation hier?

Steve Lorimer
quelle

Antworten:

98

Das +im Ausdruck +[](){}ist der unäre +Operator. Es ist wie folgt in [expr.unary.op] / 7 definiert:

Der Operand des unären +Operators muss eine arithmetische, nicht skalierte Aufzählung oder einen Zeigertyp haben, und das Ergebnis ist der Wert des Arguments.

Das Lambda ist nicht vom arithmetischen Typ usw., kann aber konvertiert werden:

[expr.prim.lambda] / 3

Der Typ des Lambda-Ausdrucks [...] ist ein eindeutiger, unbenannter Nicht-Union-Klassentyp - der so genannte Closure-Typ - dessen Eigenschaften im Folgenden beschrieben werden.

[expr.prim.lambda] / 6

Der Schließungstyp für einen Lambda-Ausdruck ohne Lambda-Erfassung verfügt über eine publicNicht- virtualNicht- explicit constKonvertierungsfunktion zum Zeiger auf eine Funktion mit denselben Parameter- und Rückgabetypen wie der Funktionsaufrufoperator des Schließungstyps. Der von dieser Konvertierungsfunktion zurückgegebene Wert ist die Adresse einer Funktion, die beim Aufrufen den gleichen Effekt hat wie das Aufrufen des Funktionsaufrufoperators des Schließungstyps.

Daher +erzwingt der Unäre die Konvertierung in den Funktionszeigertyp, der für dieses Lambda gilt void (*)(). Daher ist der Typ des Ausdrucks +[](){}dieser Funktionszeigertyp void (*)().

Die zweite Überladung void foo(void (*f)())wird zu einer exakten Übereinstimmung in der Rangfolge für die Überlastungsauflösung und wird daher eindeutig ausgewählt (da die erste Überladung KEINE genaue Übereinstimmung ist).


Die Lambda [](){}kann umgewandelt werden std::function<void()>über die nicht explizit Vorlage Ctor von std::function, die jede Art nimmt , die die erfüllt Callableund CopyConstructibleAnforderungen.

Das Lambda kann auch void (*)()über die Konvertierungsfunktion des Verschlusstyps umgerechnet werden (siehe oben).

Beide sind benutzerdefinierte Konvertierungssequenzen und haben denselben Rang. Aus diesem Grund schlägt die Überlastungsauflösung im ersten Beispiel aufgrund von Mehrdeutigkeiten fehl .


Laut Cassio Neri, unterstützt durch ein Argument von Daniel Krügler, sollte dieser unäre +Trick ein bestimmtes Verhalten sein, dh Sie können sich darauf verlassen (siehe Diskussion in den Kommentaren).

Trotzdem würde ich empfehlen, eine explizite Umwandlung in den Funktionszeigertyp zu verwenden, wenn Sie die Mehrdeutigkeit vermeiden möchten: Sie müssen SO nicht fragen, was funktioniert und warum es funktioniert;)

dyp
quelle
3
@Fred AFAIK-Member-Funktionszeiger können nicht in Nicht-Member-Funktionszeiger konvertiert werden, geschweige denn in Funktionswerte. Sie können eine Elementfunktion über std::bindan ein std::functionObjekt binden , das ähnlich wie ein Funktionswert aufgerufen werden kann.
Dyp
2
@DyP: Ich glaube, wir können uns auf das Knifflige verlassen. Angenommen, eine Implementierung trägt operator +()zu einem zustandslosen Schließungstyp bei. Angenommen, dieser Operator gibt etwas anderes als den Zeiger auf die Funktion zurück, in die der Verschlusstyp konvertiert wird. Dies würde dann das beobachtbare Verhalten eines Programms ändern, das gegen 5.1.2 / 3 verstößt. Bitte lassen Sie mich wissen, wenn Sie dieser Argumentation zustimmen.
Cassio Neri
2
@CassioNeri Ja, das ist der Punkt, an dem ich nicht sicher bin. Ich bin damit einverstanden, dass sich das beobachtbare Verhalten beim Hinzufügen eines ändern kann operator +, aber dies ist vergleichbar mit der Situation, mit der es zunächst keine gibt operator +. Es ist jedoch nicht festgelegt, dass der Verschlusstyp keine operator +Überlastung aufweisen soll. "Eine Implementierung kann den Verschlusstyp anders definieren als nachstehend beschrieben, vorausgesetzt, dies ändert das beobachtbare Verhalten des Programms nicht anders als durch [...]", aber IMO, das einen Operator hinzufügt, ändert den Verschlusstyp nicht in etwas anderes als das, was ist "unten beschrieben".
Dyp
3
@DyP: Die Situation, in der es keine gibt, operator +()ist genau die, die vom Standard beschrieben wird. Der Standard ermöglicht es einer Implementierung, etwas anderes als das Angegebene zu tun. Zum Beispiel hinzufügen operator +(). Wenn dieser Unterschied jedoch von einem Programm beobachtet werden kann, ist er illegal. Einmal habe ich in comp.lang.c ++ gefragt, ob ein Closure-Typ ein typedef für result_typeund das andere typedefserforderlich machen könnte, um sie anpassbar zu machen (zum Beispiel von std::not1). Mir wurde gesagt, dass dies nicht möglich sei, da dies beobachtbar sei. Ich werde versuchen, den Link zu finden.
Cassio Neri
5
VS15 gibt Ihnen diesen lustigen Fehler: test.cpp (543): Fehler C2593: 'operator +' ist mehrdeutig t \ test.cpp (543): Hinweis: könnte 'eingebauter C ++ - Operator + (void (__cdecl *) (void) sein )) 't \ test.cpp (543): Hinweis: oder' eingebauter C ++ - Operator + (void (__stdcall *) (void)) 't \ test.cpp (543): Hinweis: oder' eingebauter C ++ - Operator + (void (__fastcall *) (void)) 't \ test.cpp (543): note: oder' integrierter C ++ - Operator + (void (__vectorcall *) (void)) 't \ test.cpp (543): note : beim Versuch, die Argumentliste '(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>) abzugleichen test.cpp (543): Fehler C2088:' + ': illegal für Klasse
Ed Lambert