Ich suche nach den Regeln, nach denen C ++ - Vorlagen als Argumente übergeben werden.
Dies wird von C ++ unterstützt, wie in einem Beispiel hier gezeigt:
#include <iostream>
void add1(int &v)
{
v+=1;
}
void add2(int &v)
{
v+=2;
}
template <void (*T)(int &)>
void doOperation()
{
int temp=0;
T(temp);
std::cout << "Result is " << temp << std::endl;
}
int main()
{
doOperation<add1>();
doOperation<add2>();
}
Das Erlernen dieser Technik ist jedoch schwierig. Das Googeln nach "Funktion als Vorlagenargument" führt nicht zu viel. Und die klassischen C ++ - Vorlagen The Complete Guide diskutieren es überraschenderweise auch nicht (zumindest nicht aus meiner Suche).
Die Fragen, die ich habe, sind, ob dies gültiges C ++ ist (oder nur eine weit verbreitete Erweiterung).
Gibt es auch eine Möglichkeit, einen Funktor mit derselben Signatur während dieser Art des Vorlagenaufrufs austauschbar mit expliziten Funktionen zu verwenden?
Die folgende tut nicht Arbeit in dem obigen Programm, zumindest in Visual C ++ , da die Syntax offensichtlich falsch ist. Es wäre schön, eine Funktion für einen Funktor ausschalten zu können und umgekehrt, ähnlich wie Sie einen Funktionszeiger oder Funktor an den std :: sort-Algorithmus übergeben können, wenn Sie eine benutzerdefinierte Vergleichsoperation definieren möchten.
struct add3 {
void operator() (int &v) {v+=3;}
};
...
doOperation<add3>();
Hinweise auf einen oder zwei Weblinks oder eine Seite im C ++ - Vorlagenbuch sind willkommen!
quelle
-std=gnu++17
. Kann ich das Ergebnis eines Capture-freien Lambda-Constexpr-Konvertierungsoperators in C ++ 17 als nicht typisiertes Argument für eine Funktionszeigervorlage verwenden? .Antworten:
Ja, es ist gültig.
Die übliche Lösung ist, dass es auch mit Funktoren funktioniert:
was nun als entweder bezeichnet werden kann:
Sehen Sie es live
Das Problem dabei ist, dass, wenn es für den Compiler schwierig ist, den Aufruf zu inline
add2
, da der Compiler nur weiß, dass ein Funktionszeigertypvoid (*)(int &)
übergeben wirddoOperation
. (Alsadd3
Funktor kann er jedoch leicht eingefügt werden. Hier weiß der Compiler, dass ein Objekt vom Typadd3
an die Funktion übergeben wird, was bedeutet, dass die aufzurufende Funktionadd3::operator()
nicht nur ein unbekannter Funktionszeiger ist.)quelle
template <typename F> void doOperation(F&& f) {/**/}
) zu verwenden, kann bind beispielsweise einen bind-Ausdruck übergeben, anstatt ihn zu binden.Vorlagenparameter können entweder nach Typ (Typname T) oder nach Wert (int X) parametrisiert werden.
Die "traditionelle" C ++ - Methode zum Vorlagen eines Codeteils besteht darin, einen Funktor zu verwenden - das heißt, der Code befindet sich in einem Objekt, und das Objekt gibt dem Code somit einen eindeutigen Typ.
Bei der Arbeit mit herkömmlichen Funktionen funktioniert diese Technik nicht gut, da eine Änderung des Typs keine bestimmte Funktion anzeigt, sondern nur die Signatur vieler möglicher Funktionen angibt. So:
Entspricht nicht dem Funktorfall. In diesem Beispiel wird do_op für alle Funktionszeiger instanziiert, deren Signatur int X (int, int) ist. Der Compiler müsste ziemlich aggressiv sein, um diesen Fall vollständig zu integrieren. (Ich würde es jedoch nicht ausschließen, da die Compileroptimierung ziemlich weit fortgeschritten ist.)
Eine Möglichkeit zu erkennen, dass dieser Code nicht ganz das tut, was wir wollen, ist:
ist immer noch legal, und dies wird eindeutig nicht inline. Um ein vollständiges Inlining zu erhalten, müssen wir eine Vorlage nach Wert erstellen, damit die Funktion in der Vorlage vollständig verfügbar ist.
In diesem Fall wird jede instanziierte Version von do_op mit einer bestimmten Funktion instanziiert, die bereits verfügbar ist. Wir erwarten daher, dass der Code für do_op sehr nach "return a + b" aussieht. (Lisp-Programmierer, hör auf zu grinsen!)
Wir können auch bestätigen, dass dies näher an dem liegt, was wir wollen, weil dies:
wird nicht kompiliert. GCC sagt: "Fehler: 'func_ptr' kann nicht in einem konstanten Ausdruck erscheinen. Mit anderen Worten, ich kann do_op nicht vollständig erweitern, weil Sie mir zum Zeitpunkt des Compilers nicht genügend Informationen gegeben haben, um zu wissen, was unsere Operation ist.
Also, wenn das zweite Beispiel unsere Operation wirklich vollständig einfügt und das erste nicht, was nützt die Vorlage? Was macht es? Die Antwort lautet: Typ Zwang. Dieses Riff im ersten Beispiel wird funktionieren:
Dieses Beispiel wird funktionieren! (Ich behaupte nicht, dass es gutes C ++ ist, aber ...) Was passiert ist, ist, dass do_op um die Signaturen der verschiedenen Funktionen herum erstellt wurde und jede separate Instanziierung einen anderen Typ von Zwangscode schreibt. Der instanziierte Code für do_op mit fadd sieht also ungefähr so aus:
Im Vergleich dazu erfordert unser By-Value-Fall eine genaue Übereinstimmung mit den Funktionsargumenten.
quelle
int c = do_op(4,5,func_ptr);
die "eindeutig nicht inline" wird.Funktionszeiger können als Vorlagenparameter übergeben werden. Dies ist Teil von Standard-C ++ . In der Vorlage werden sie jedoch deklariert und als Funktionen und nicht als Zeiger auf Funktion verwendet. Bei Vorlage Instanziierung passiert man die Adresse der Funktion und nicht nur den Namen.
Beispielsweise:
Wenn Sie einen Funktionstyp als Vorlagenargument übergeben möchten:
Mehrere Antworten übergeben eine Funktorinstanz als Argument:
Mit einem Vorlagenargument können Sie diesem einheitlichen Erscheinungsbild am nächsten kommen,
do_op
indem Sie es zweimal definieren - einmal mit einem Nicht-Typparameter und einmal mit einem Typparameter.Ehrlich gesagt hatte ich wirklich erwartet, dass dies nicht kompiliert wird, aber es funktionierte für mich mit gcc-4.8 und Visual Studio 2013.
quelle
In Ihrer Vorlage
Der Parameter
T
ist ein nicht typisierter Vorlagenparameter. Dies bedeutet, dass sich das Verhalten der Vorlagenfunktion mit dem Wert des Parameters ändert (der zur Kompilierungszeit festgelegt werden muss, welche Funktionszeigerkonstanten sind).Wenn Sie etwas möchten, das sowohl mit Funktionsobjekten als auch mit Funktionsparametern funktioniert, benötigen Sie eine typisierte Vorlage. Wenn Sie dies tun, müssen Sie der Funktion zur Laufzeit auch eine Objektinstanz (entweder eine Funktionsobjektinstanz oder einen Funktionszeiger) bereitstellen.
Es gibt einige geringfügige Leistungsüberlegungen. Diese neue Version ist möglicherweise weniger effizient mit Funktionszeigerargumenten, da der bestimmte Funktionszeiger nur zur Laufzeit dereferenziert und aufgerufen wird, während Ihre Funktionszeigervorlage basierend auf dem bestimmten verwendeten Funktionszeiger optimiert werden kann (möglicherweise der Funktionsaufruf inline). Funktionsobjekte können mit der typisierten Vorlage oft sehr effizient erweitert werden, da das Besondere
operator()
jedoch vollständig vom Typ des Funktionsobjekts abhängt.quelle
Der Grund, warum Ihr Funktionsbeispiel nicht funktioniert, ist, dass Sie eine Instanz benötigen, um das aufzurufen
operator()
.quelle
Bearbeiten: Das Übergeben des Operators als Referenz funktioniert nicht. Verstehen Sie es der Einfachheit halber als Funktionszeiger. Sie senden nur den Zeiger, keine Referenz. Ich denke, Sie versuchen so etwas zu schreiben.
. .
quelle