Ich weiß, dass der folgende Code nicht kompiliert werden kann.
void baz(int i) { }
void baz() { }
class Bar
{
std::function<void()> bazFn;
public:
Bar(std::function<void()> fun = baz) : bazFn(fun){}
};
int main(int argc, char **argv)
{
Bar b;
return 0;
}
Weil std::function
gesagt wird, Überlastungsauflösung nicht zu berücksichtigen, wie ich in diesem anderen Beitrag gelesen habe .
Ich verstehe die technischen Einschränkungen, die diese Art von Lösung erzwungen haben, nicht vollständig.
Ich habe über die Phasen der Übersetzung und Vorlagen auf cppreference gelesen, aber ich kann mir keine Argumentation vorstellen, für die ich kein Gegenbeispiel finden konnte. Was und in welcher Übersetzungsphase lässt sich das oben Gesagte nicht kompilieren, wenn es einem halben Laien erklärt wird (noch neu in C ++)?
c++
templates
translation
std-function
TuRtoise
quelle
quelle
Antworten:
Dies hat eigentlich nichts mit "Übersetzungsphasen" zu tun. Es geht nur um die Konstrukteure von
std::function
.Siehe,
std::function<R(Args)>
erfordert nicht, dass die angegebene Funktion genau vom Typ istR(Args)
. Insbesondere ist es nicht erforderlich, dass ihm ein Funktionszeiger gegeben wird. Es kann einen beliebigen aufrufbaren Typ (operator()
Elementfunktionszeiger , ein Objekt mit einer Überladung von ) annehmen, solange es aufgerufen werden kann, als ob esArgs
Parameter verwendet und etwas konvertierbares zurückgibtR
(oder wenn dies der FallR
istvoid
, kann es alles zurückgeben).Dazu muss der entsprechende Konstruktor von
std::function
eine Vorlage sein :template<typename F> function(F f);
. Das heißt, es kann jeder Funktionstyp verwendet werden (vorbehaltlich der oben genannten Einschränkungen).Der Ausdruck
baz
repräsentiert eine Überlastmenge. Wenn Sie diesen Ausdruck verwenden, um den Überlastungssatz aufzurufen, ist das in Ordnung. Wenn Sie diesen Ausdruck als Parameter für eine Funktion verwenden, die einen bestimmten Funktionszeiger verwendet, kann C ++ die Überlastmenge auf einen einzelnen Aufruf reduzieren und so in Ordnung bringen.Sobald eine Funktion jedoch eine Vorlage ist und Sie die Ableitung von Vorlagenargumenten verwenden, um herauszufinden, um welchen Parameter es sich handelt, kann C ++ die korrekte Überladung im Überlastungssatz nicht mehr ermitteln. Sie müssen es also direkt angeben.
quelle
function
Klassenvorlage ist irrelevant . Was zählt, ist der Vorlagenparameter auf dem Konstruktor, den Sie aufrufen. Welches ist nurtypename F
: aka, jeder Typ.Eine Überlastungsauflösung tritt nur auf, wenn (a) Sie den Namen einer Funktion / eines Operators aufrufen oder (b) ihn in einen Zeiger (auf eine Funktion oder eine Elementfunktion) mit einer expliziten Signatur umwandeln.
Beides tritt hier nicht auf.
std::function
Nimmt jedes Objekt, das mit seiner Signatur kompatibel ist . Es wird kein spezieller Funktionszeiger benötigt. (Ein Lambda ist keine Standardfunktion, und eine Standardfunktion ist kein Lambda.)In meinen Homebrew-Funktionsvarianten
R(Args...)
akzeptiere ich zur SignaturR(*)(Args...)
aus genau diesem Grund auch ein Argument (eine genaue Übereinstimmung). Dies bedeutet jedoch, dass die Signaturen mit "exakter Übereinstimmung" über die "kompatiblen" Signaturen angehoben werden.Das Hauptproblem besteht darin, dass ein Überlastungssatz kein C ++ - Objekt ist. Sie können einen Überlastungssatz benennen, ihn jedoch nicht "nativ" weitergeben.
Jetzt können Sie einen Pseudo-Überlastungssatz einer Funktion wie folgt erstellen:
Dadurch wird ein einzelnes C ++ - Objekt erstellt, das eine Überlastungsauflösung für einen Funktionsnamen durchführen kann.
Wenn wir die Makros erweitern, erhalten wir:
das ist nervig zu schreiben. Eine einfachere, nur etwas weniger nützliche Version finden Sie hier:
Wir haben ein Lambda, das eine beliebige Anzahl von Argumenten akzeptiert und diese dann perfekt weiterleitet
baz
.Dann:
funktioniert. Wir verschieben die Überlastungsauflösung auf das Lambda, in dem wir speichern
fun
, anstatt zu übergebenfun
einen Überlastungssatz direkt zu übergeben (den er nicht auflösen kann).Es gab mindestens einen Vorschlag, eine Operation in der C ++ - Sprache zu definieren, die einen Funktionsnamen in ein Überlastungssatzobjekt konvertiert. Bis ein solcher Standardvorschlag im Standard enthalten ist, ist das
OVERLOADS_OF
Makro nützlich.Sie könnten noch einen Schritt weiter gehen und den Zeiger auf einen kompatiblen Funktionszeiger unterstützen.
aber das wird langsam stumpf.
Live Beispiel .
quelle
Das Problem hier ist, dass dem Compiler nichts sagt, wie er die Funktion zum Zeigerzerfall ausführen soll. Wenn Sie haben
Dann würde Code funktionieren, da der Compiler jetzt weiß, welche Funktion Sie möchten, da Sie einen konkreten Typ zuweisen.
Wenn Sie verwenden
std::function
, rufen Sie den Funktionsobjektkonstruktor auf, der die Form hatund da es sich um eine Vorlage handelt, muss der Typ des übergebenen Objekts abgeleitet werden. Da
baz
es sich um eine überladene Funktion handelt, kann kein einzelner Typ abgeleitet werden, sodass der Vorlagenabzug fehlschlägt und Sie eine Fehlermeldung erhalten. Sie müssten verwendeneinen einzelnen Typ erzwingen und den Abzug zulassen.
quelle
Zu diesem Zeitpunkt entscheidet der Compiler, welche Überladung an den
std::function
Konstruktor übergeben werden soll. Er weiß lediglich, dass derstd::function
Konstruktor für einen beliebigen Typ vorgesehen ist. Es ist nicht in der Lage, beide Überladungen zu versuchen und festzustellen, dass die erste nicht kompiliert wird, die zweite jedoch.Die Möglichkeit, dies zu beheben, besteht darin, dem Compiler explizit mitzuteilen, welche Überladung Sie mit einem
static_cast
:quelle