Was ist der Unterschied zwischen verschiedenen Arten, eine Funktion als Argument an eine andere Funktion zu übergeben?

8

Ich habe die Situation, dass eine Funktion eine von mehreren möglichen Funktionen aufruft. Dies scheint ein guter Ort zu sein, um eine Funktion als Parameter zu übergeben. In dieser Quoara-Antwort von Zubkov gibt es drei Möglichkeiten, dies zu tun.

int g(int x(int)) { return x(1); }
int g(int (*x)(int)) { return x(1); }
int g(int (&x)(int)) { return x(1); }
...
int f(int n) { return n*2; }
g(f); // all three g's above work the same

Wann sollte welche Methode angewendet werden? Was gibt es Unterschiede? Ich bevorzuge den einfachsten Ansatz. Warum sollte nicht immer der erste Weg verwendet werden?

In meiner Situation wird die Funktion nur einmal aufgerufen und ich möchte sie einfach halten. Ich habe es mit Pass-by-Zeiger arbeiten und ich rufe es einfach mit g(myFunc)wo myFuncist die Funktion, die zuletzt aufgerufen wird.

Nordländer
quelle
3
Keines von denen. Verwenden Sie einen Vorlagenparameter.
LF
2
Die ersten beiden sind völlig gleichwertig. Der dritte ist fast der gleiche wie die ersten beiden, außer dass ein l-Wert erforderlich ist. g(+f);funktioniert für die ersten beiden, aber nicht für die dritte.
Raymond Chen
@RaymondChen "Die ersten beiden sind völlig gleichwertig", dann ist meiner Meinung nach die erste offensichtlich die richtige Wahl, da sie einfacher ist. Warum mit einem Zeiger komplizieren?
Nordländer
1
Auf der anderen Seite, in int g(int x(int)), xist ein Zeiger , obwohl es nicht wie man sieht. Die entsprechende globale Deklaration int x(int);deklariert eine Funktion, keinen Funktionszeiger.
Raymond Chen
Ein Godbolt-Link zur Sicherung der Behauptung von @ RaymondChen . Beachten Sie, dass die ausgegebenen Baugruppen auch xals Zeiger gekennzeichnet sind.
Eric

Antworten:

3

Wenn Sie den Kommentar von LF erweitern, ist es oft besser, Funktionszeiger vollständig zu meiden und in Bezug auf aufrufbare Objekte (Dinge, die definieren operator()) zu arbeiten. Mit all den folgenden Optionen können Sie dies tun:

#include <type_traits>

// (1) unrestricted template parameter, like <algorithm> uses
template<typename Func>
int g(Func x) { return x(1); }

// (2) restricted template parameter to produce possibly better errors
template<
    typename Func,
    typename=std::enable_if_t<std::is_invocable_r_v<int, Func, int>>
>
int g(Func x) { return std::invoke(x, 1); }

// (3) template-less, trading a reduction in code size for runtime overhead and heap use
int g(std::function<int(int)> x) { return x(1); }

Wichtig ist, dass all diese Funktionen für Lambda-Funktionen mit Captures verwendet werden können, im Gegensatz zu Ihren Optionen:

int y = 2;
int ret = g([y](int v) {
    return y + v;
});
Eric
quelle
Wie heißen diese? Auch wie sind diese besser?
Nordländer
Sie werden genauso aufgerufen wie Ihre obigen Unterschriften. Sie sind besser, weil sie mit Lambdas und anderen staatlichen Funktionen arbeiten. Beachten Sie, dass alle <algorithm>diesen Ansatz verwenden, um Rückruffunktionen zu akzeptieren.
Eric
OK. Zur Bestätigung müssen Sie die Vorlagenfunktion nicht mit einer Vorlagenkennung aufrufen. Zum Beispiel brauchen Sie nicht g<int>(myFunc)nur g(myFunc)?
Nordländer
Richtig, die Idee ist, den Tyoe-Parameter ableiten zu lassen
Eric
Würden Vorlagen weiterhin funktionieren, wenn Sie mehr als eine Funktion als Parameter übergeben?
Nordländer